{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calc-editor",
  "title": "Calc Editor",
  "description": "Ridiculously typed CSS math editor for calc/clamp/min/max with compile-time dimensional analysis (length±length ✓, length±angle ✗, ÷ by non-number ✗). Ships a full runtime parser/evaluator and a fluid-type clamp() playground.",
  "dependencies": [],
  "registryDependencies": [
    "ridiculous-type-kit",
    "button",
    "popover",
    "input"
  ],
  "files": [
    {
      "path": "src/components/ui/calc-editor/index.ts",
      "content": "export type {\n  CalcEditorPanelProps,\n  CalcEditorProps,\n  ExpressionFieldProps,\n  FluidTypePlaygroundProps,\n  TokenPaletteProps,\n} from \"./calc-editor\"\nexport {\n  CalcEditor,\n  CalcEditorPanel,\n  ExpressionField,\n  FluidTypePlayground,\n  TokenPalette,\n} from \"./calc-editor\"\nexport type {\n  ComputeContext,\n  EvaluateResult,\n  Token,\n  TokenType,\n} from \"./calc-editor.helpers\"\nexport {\n  calcDimension,\n  computeCalc,\n  dimensionOf,\n  evaluateCalc,\n  formatCalc,\n  parseCalc,\n  tokenizeCalc,\n} from \"./calc-editor.helpers\"\nexport type {\n  ArgCountOf,\n  CalcFn,\n  CalcFunctionName,\n  CalcLiteral,\n  CalcNode,\n  CalcString,\n  CalcStringMap,\n  Dimension,\n  DimensionOfCalc,\n  FunctionOf,\n} from \"./calc-editor.types\"\nexport { cssCalc } from \"./calc-editor.types\"\n",
      "type": "registry:ui",
      "target": "components/ui/calc-editor/index.ts"
    },
    {
      "path": "src/components/ui/calc-editor/calc-editor.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useId, useRef, useState } from \"react\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\nimport { cn } from \"@/lib/utils\"\nimport { computeCalc, evaluateCalc, parseCalc } from \"./calc-editor.helpers\"\nimport type {\n  CalcFn,\n  CalcString,\n  CalcStringMap,\n  Dimension,\n} from \"./calc-editor.types\"\n\n// ---------------------------------------------------------------------------\n// Shared props\n// ---------------------------------------------------------------------------\n\nexport interface CalcEditorPanelProps<\n  TFn extends CalcFn | undefined = undefined,\n> {\n  value: CalcString | (string & {})\n  onChange: (\n    value: TFn extends CalcFn ? CalcStringMap[TFn] : CalcString,\n  ) => void\n  /** Lock the outer function; narrows `onChange` to that flavor. */\n  fn?: TFn\n  /** Viewport width (px) used for the computed-value readout. Default 1280. */\n  referenceViewport?: number\n  className?: string\n  \"aria-label\"?: string\n}\n\nexport interface CalcEditorProps<TFn extends CalcFn | undefined = undefined>\n  extends CalcEditorPanelProps<TFn> {}\n\nconst ALL_FNS: readonly CalcFn[] = [\"calc\", \"clamp\", \"min\", \"max\"] as const\n\n// Seed expression text when switching to / starting on a function.\nfunction seedFor(fn: CalcFn, inner: string): string {\n  const body = inner.trim() || \"1rem\"\n  switch (fn) {\n    case \"calc\":\n      return `calc(${body})`\n    case \"clamp\":\n      return `clamp(1rem, ${body}, 3rem)`\n    case \"min\":\n      return `min(${body}, 2rem)`\n    case \"max\":\n      return `max(${body}, 2rem)`\n  }\n}\n\n// Pull the \"inner\" of a calc()/single-arg expression for re-wrapping.\nfunction innerOf(expr: string): string {\n  const node = parseCalc(expr)\n  if (node && node.kind === \"fn\" && node.name === \"calc\") {\n    // re-serialize the single calc arg roughly by stripping the wrapper\n    const m = expr.trim().match(/^calc\\((.*)\\)$/s)\n    if (m) return m[1].trim()\n  }\n  return expr.trim()\n}\n\n// ---------------------------------------------------------------------------\n// CalcEditor — popover-wrapped\n// ---------------------------------------------------------------------------\n\nexport function CalcEditor<TFn extends CalcFn | undefined = undefined>(\n  props: CalcEditorProps<TFn>,\n) {\n  const {\n    value,\n    className,\n    \"aria-label\": ariaLabel = \"Edit a CSS math expression\",\n  } = props\n  const { dimension } = evaluateCalc(value)\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <Button\n          variant=\"outline\"\n          className={cn(\"h-9 gap-2 px-3 font-mono\", className)}\n          aria-label={ariaLabel}\n        >\n          <span aria-hidden=\"true\" className=\"text-foreground/60 italic\">\n            fx\n          </span>\n          <span className=\"max-w-[180px] truncate text-xs\">{value}</span>\n          <DimensionBadge dimension={dimension} />\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-auto p-0\" align=\"start\">\n        <CalcEditorPanel {...props} />\n      </PopoverContent>\n    </Popover>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// CalcEditorPanel — inline\n// ---------------------------------------------------------------------------\n\nexport function CalcEditorPanel<TFn extends CalcFn | undefined = undefined>({\n  value,\n  onChange,\n  fn: fnProp,\n  referenceViewport = 1280,\n  className,\n  \"aria-label\": ariaLabel = \"CSS math value editor\",\n}: CalcEditorPanelProps<TFn>) {\n  const initialFn = fnProp ?? detectFn(value) ?? \"calc\"\n  const [activeFn, setActiveFn] = useState<CalcFn>(initialFn)\n  const [expr, setExpr] = useState<string>(value)\n  const lastEmittedRef = useRef<string | null>(null)\n\n  // Resync from external value (skip our own emits). A locked `fnProp` pins the\n  // active function, so only auto-switch the tab when there is no lock.\n  useEffect(() => {\n    if (value === lastEmittedRef.current) return\n    setExpr(value)\n    const detected = detectFn(value)\n    if (!fnProp && detected) setActiveFn(detected)\n  }, [value, fnProp])\n\n  const result = evaluateCalc(expr)\n  const valid = result.error === null && result.dimension !== null\n\n  const commit = (next: string) => {\n    setExpr(next)\n    const r = evaluateCalc(next)\n    if (r.error === null && r.dimension !== null) {\n      lastEmittedRef.current = next\n      onChange(next as never)\n    }\n  }\n\n  const switchFn = (next: CalcFn) => {\n    setActiveFn(next)\n    commit(seedFor(next, innerOf(expr)))\n  }\n\n  const insert = (token: string) => {\n    commit(expr + token)\n  }\n\n  const available: readonly CalcFn[] = fnProp ? [fnProp] : ALL_FNS\n\n  return (\n    <fieldset\n      className={cn(\n        \"m-0 w-[460px] space-y-3 border-0 bg-background p-3\",\n        className,\n      )}\n      aria-label={ariaLabel}\n    >\n      <FunctionTabs\n        value={activeFn}\n        onChange={switchFn}\n        available={available}\n      />\n      <ExpressionField\n        value={expr}\n        onChange={commit}\n        dimension={valid ? result.dimension : null}\n        error={result.error}\n      />\n      <TokenPalette onInsert={insert} />\n      <ResultReadout\n        expr={expr}\n        dimension={result.dimension}\n        error={result.error}\n        referenceViewport={referenceViewport}\n      />\n      <FluidTypePlayground expression={expr} />\n    </fieldset>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// FunctionTabs (internal)\n// ---------------------------------------------------------------------------\n\ninterface FunctionTabsProps {\n  value: CalcFn\n  onChange: (fn: CalcFn) => void\n  available?: readonly CalcFn[]\n}\n\nfunction FunctionTabs({\n  value,\n  onChange,\n  available = ALL_FNS,\n}: FunctionTabsProps) {\n  return (\n    <div\n      role=\"tablist\"\n      aria-label=\"Math function\"\n      className=\"flex gap-1 border-b text-xs\"\n    >\n      {available.map((fn) => (\n        <button\n          key={fn}\n          type=\"button\"\n          role=\"tab\"\n          aria-selected={value === fn}\n          onClick={() => onChange(fn)}\n          className={cn(\n            \"px-3 py-1.5 font-mono transition-colors\",\n            value === fn\n              ? \"border-primary border-b-2 text-foreground\"\n              : \"text-muted-foreground hover:text-foreground\",\n          )}\n        >\n          {fn}\n        </button>\n      ))}\n    </div>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// ExpressionField (public)\n// ---------------------------------------------------------------------------\n\nexport interface ExpressionFieldProps {\n  value: string\n  onChange: (value: string) => void\n  dimension?: Dimension | null\n  error?: string | null\n  className?: string\n}\n\nexport function ExpressionField({\n  value,\n  onChange,\n  dimension,\n  error,\n  className,\n}: ExpressionFieldProps) {\n  const id = useId()\n  return (\n    <div className={cn(\"space-y-1\", className)}>\n      <label htmlFor={id} className=\"sr-only\">\n        Expression\n      </label>\n      <div className=\"relative\">\n        <Input\n          id={id}\n          value={value}\n          spellCheck={false}\n          autoComplete=\"off\"\n          onChange={(e) => onChange(e.target.value)}\n          className={cn(\n            \"h-10 pr-24 font-mono text-sm\",\n            error ? \"border-destructive focus-visible:ring-destructive\" : \"\",\n          )}\n          aria-invalid={error ? true : undefined}\n        />\n        <div className=\"absolute top-1/2 right-2 -translate-y-1/2\">\n          <DimensionBadge dimension={dimension ?? null} />\n        </div>\n      </div>\n      {error ? (\n        <p className=\"text-destructive text-xs\" role=\"alert\">\n          {error}\n        </p>\n      ) : null}\n    </div>\n  )\n}\n\nfunction DimensionBadge({ dimension }: { dimension: Dimension | null }) {\n  return (\n    <span\n      className={cn(\n        \"rounded px-1.5 py-0.5 font-mono text-[10px]\",\n        dimension\n          ? \"bg-emerald-500/15 text-emerald-600 dark:text-emerald-400\"\n          : \"bg-muted text-muted-foreground\",\n      )}\n    >\n      {dimension ?? \"—\"}\n    </span>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// TokenPalette (public)\n// ---------------------------------------------------------------------------\n\nexport interface TokenPaletteProps {\n  onInsert: (token: string) => void\n  className?: string\n}\n\nconst OPERATOR_TOKENS: ReadonlyArray<{ label: string; insert: string }> = [\n  { label: \"+\", insert: \" + \" },\n  { label: \"-\", insert: \" - \" },\n  { label: \"*\", insert: \" * \" },\n  { label: \"/\", insert: \" / \" },\n]\nconst UNIT_TOKENS: readonly string[] = [\"px\", \"rem\", \"em\", \"%\", \"vw\", \"vh\"]\nconst FN_TOKENS: ReadonlyArray<{ label: string; insert: string }> = [\n  { label: \"clamp()\", insert: \"clamp(, , )\" },\n  { label: \"min()\", insert: \"min(, )\" },\n  { label: \"max()\", insert: \"max(, )\" },\n  { label: \"var()\", insert: \"var(--)\" },\n  { label: \"( )\", insert: \"()\" },\n]\n\nexport function TokenPalette({ onInsert, className }: TokenPaletteProps) {\n  return (\n    <div className={cn(\"space-y-1.5\", className)}>\n      <div className=\"flex flex-wrap gap-1\">\n        {OPERATOR_TOKENS.map((t) => (\n          <PaletteButton\n            key={t.label}\n            label={t.label}\n            onClick={() => onInsert(t.insert)}\n          />\n        ))}\n        <span className=\"mx-1 w-px self-stretch bg-border\" aria-hidden=\"true\" />\n        {UNIT_TOKENS.map((u) => (\n          <PaletteButton key={u} label={u} onClick={() => onInsert(u)} />\n        ))}\n      </div>\n      <div className=\"flex flex-wrap gap-1\">\n        {FN_TOKENS.map((t) => (\n          <PaletteButton\n            key={t.label}\n            label={t.label}\n            onClick={() => onInsert(t.insert)}\n          />\n        ))}\n      </div>\n    </div>\n  )\n}\n\nfunction PaletteButton({\n  label,\n  onClick,\n}: {\n  label: string\n  onClick: () => void\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      className=\"rounded border bg-muted px-2 py-1 font-mono text-xs hover:bg-accent\"\n    >\n      {label}\n    </button>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// ResultReadout (internal)\n// ---------------------------------------------------------------------------\n\ninterface ResultReadoutProps {\n  expr: string\n  dimension: Dimension | null\n  error: string | null\n  referenceViewport: number\n}\n\nfunction ResultReadout({\n  expr,\n  dimension,\n  error,\n  referenceViewport,\n}: ResultReadoutProps) {\n  if (error) return null\n  const node = parseCalc(expr)\n  const computed =\n    node && dimension === \"length\"\n      ? computeCalc(node, { viewport: referenceViewport })\n      : null\n  return (\n    <div className=\"flex items-center justify-between rounded bg-muted/50 px-2 py-1 text-muted-foreground text-xs\">\n      <span>\n        resolves to <span className=\"text-foreground\">{dimension ?? \"—\"}</span>\n      </span>\n      {computed !== null ? (\n        <span className=\"font-mono\">\n          ≈ {round(computed)}px @ {referenceViewport}px\n        </span>\n      ) : null}\n    </div>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// FluidTypePlayground (public) — the showcase\n// ---------------------------------------------------------------------------\n\nexport interface FluidTypePlaygroundProps {\n  expression: string\n  minViewport?: number\n  maxViewport?: number\n  className?: string\n}\n\nexport function FluidTypePlayground({\n  expression,\n  minViewport = 320,\n  maxViewport = 1920,\n  className,\n}: FluidTypePlaygroundProps) {\n  const id = useId()\n  const [vw, setVw] = useState<number>(\n    Math.round((minViewport + maxViewport) / 2),\n  )\n  const node = parseCalc(expression)\n  const computed = node ? computeCalc(node, { viewport: vw, basis: vw }) : null\n\n  return (\n    <div className={cn(\"space-y-2 rounded-lg border p-3\", className)}>\n      <div className=\"flex items-center justify-between text-xs\">\n        <span className=\"font-medium text-muted-foreground\">\n          Fluid-type playground\n        </span>\n        <span className=\"font-mono\">\n          {computed !== null ? `${round(computed)}px` : \"—\"}\n        </span>\n      </div>\n      <label htmlFor={id} className=\"flex flex-col gap-1 text-xs\">\n        <span className=\"flex justify-between text-muted-foreground\">\n          <span>viewport width</span>\n          <span className=\"font-mono\">{vw}px</span>\n        </span>\n        <input\n          id={id}\n          type=\"range\"\n          aria-label=\"Viewport width\"\n          min={minViewport}\n          max={maxViewport}\n          step={1}\n          value={vw}\n          onChange={(e) => setVw(Number(e.target.value))}\n        />\n      </label>\n      {/* live visual: a bar whose size tracks the computed length */}\n      {computed !== null ? (\n        <div className=\"flex h-6 items-center\">\n          <div\n            className=\"h-2 rounded bg-primary transition-[width] duration-75\"\n            style={{ width: `${Math.min(Math.max(computed, 0), 320)}px` }}\n            aria-hidden=\"true\"\n          />\n        </div>\n      ) : null}\n    </div>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// helpers\n// ---------------------------------------------------------------------------\n\nfunction detectFn(value: string): CalcFn | null {\n  const v = value.trim()\n  if (v.startsWith(\"calc(\")) return \"calc\"\n  if (v.startsWith(\"clamp(\")) return \"clamp\"\n  if (v.startsWith(\"min(\")) return \"min\"\n  if (v.startsWith(\"max(\")) return \"max\"\n  return null\n}\n\nfunction round(n: number): number {\n  return Math.round(n * 100) / 100\n}\n",
      "type": "registry:ui",
      "target": "components/ui/calc-editor/calc-editor.tsx"
    },
    {
      "path": "src/components/ui/calc-editor/calc-editor.types.ts",
      "content": "// =====================================================================\n// calc-editor.types.ts\n//\n// The \"ridiculous\" tier: compile-time DIMENSIONAL ANALYSIS of a CSS math\n// expression. Built entirely on `ridiculous-type-kit`. The strict\n// validator `CalcLiteral<S>` resolves to `S` when the expression is\n// dimensionally valid (length ± length ✓, length ± angle ✗,\n// length × length ✗, ÷ by non-number ✗) and to `never` otherwise.\n//\n// Structure mirrors easing-picker.types.ts:\n//   kit imports → evaluator → CalcLiteral + cssCalc → suggestion strings\n//   → utility types → internal discriminated-union state.\n// =====================================================================\n\nimport type {\n  Dimension,\n  DimensionOf,\n  SplitByComma,\n  SplitBySpace,\n  Trim,\n} from \"@/lib/ridiculous-type-kit\"\n\n// =====================================================================\n// 1. DIMENSIONAL EVALUATOR (the engine)\n// =====================================================================\n\n/** Combine two dimensions under one binary operator → Dimension | never. */\ntype CombineDim<\n  A extends Dimension,\n  Op extends string,\n  B extends Dimension,\n> = Op extends \"+\" | \"-\"\n  ? A extends B\n    ? A\n    : never\n  : Op extends \"*\"\n    ? A extends \"number\"\n      ? B\n      : B extends \"number\"\n        ? A\n        : never\n    : Op extends \"/\"\n      ? B extends \"number\"\n        ? A\n        : never\n      : never\n\n// Math constants are number-dimension leaves (L3).\ntype MathConstant = \"pi\" | \"e\" | \"infinity\" | \"-infinity\" | \"NaN\"\n\n/**\n * Evaluate a single leaf to a Dimension:\n *  - math constant → \"number\"\n *  - `(...)` → recurse on the inner expression (consumes one Depth unit;\n *    on exhaustion weak-accepts as \"number\" per L2)\n *  - `name(...)` → dispatch to EvalFn (nested calc/min/max/clamp)\n *  - otherwise → DimensionOf (a plain value like 10px / 2 / 50%)\n */\ntype EvalLeaf<S extends string, Depth extends unknown[]> =\n  Trim<S> extends MathConstant\n    ? \"number\"\n    : Trim<S> extends `(${infer Inner})`\n      ? Depth extends [unknown, ...infer Rest]\n        ? EvalExpr<SplitBySpace<Trim<Inner>>, Rest>\n        : \"number\" // L2: depth budget exhausted → weak-accept\n      : Trim<S> extends `${infer Name}(${infer Args})`\n        ? Depth extends [unknown, ...infer Rest]\n          ? EvalFn<Trim<Name>, Args, Rest>\n          : \"number\" // L2\n        : DimensionOf<Trim<S>>\n\n/**\n * Evaluate a space-separated token list (operand op operand op ...).\n * Right-associative (L1) — dimensionally lossless for these operator rules.\n */\ntype EvalExpr<\n  Tokens extends string[],\n  Depth extends unknown[],\n> = Tokens extends [infer Only extends string]\n  ? EvalLeaf<Only, Depth>\n  : Tokens extends [\n        infer First extends string,\n        infer Op extends string,\n        ...infer Rest extends string[],\n      ]\n    ? EvalLeaf<First, Depth> extends infer A\n      ? A extends Dimension\n        ? EvalExpr<Rest, Depth> extends infer B\n          ? B extends Dimension\n            ? CombineDim<A, Op, B>\n            : never\n          : never\n        : never\n      : never\n    : never\n\n/** Fold a comma-separated arg list requiring every arg to share one dimension. */\ntype EvalVariadicSame<\n  Args extends string[],\n  Depth extends unknown[],\n  Acc extends Dimension | \"init\" = \"init\",\n> = Args extends [infer Head extends string, ...infer Tail extends string[]]\n  ? EvalExpr<SplitBySpace<Trim<Head>>, Depth> extends infer D\n    ? D extends Dimension\n      ? Acc extends \"init\"\n        ? EvalVariadicSame<Tail, Depth, D>\n        : Acc extends D\n          ? EvalVariadicSame<Tail, Depth, Acc>\n          : never\n      : never\n    : never\n  : Acc extends Dimension\n    ? Acc\n    : never // empty list → never (min/max need ≥1)\n\n/** clamp(min, preferred, max): exactly 3 args, all the same dimension. */\ntype EvalClamp<Args extends string[], Depth extends unknown[]> = Args extends [\n  string,\n  string,\n  string,\n]\n  ? EvalVariadicSame<Args, Depth>\n  : never\n\n/** Dispatch a function call by name. */\ntype EvalFn<\n  Name extends string,\n  Args extends string,\n  Depth extends unknown[],\n> = Name extends \"calc\"\n  ? EvalExpr<SplitBySpace<Trim<Args>>, Depth>\n  : Name extends \"min\" | \"max\"\n    ? EvalVariadicSame<SplitByComma<Args>, Depth>\n    : Name extends \"clamp\"\n      ? EvalClamp<SplitByComma<Args>, Depth>\n      : never // unknown fn (var/env/attr/rgb/...) → never at strict tier (L6)\n\n// Nesting budget (L2): 8 levels of parens / function calls.\ntype Depth8 = [\n  unknown,\n  unknown,\n  unknown,\n  unknown,\n  unknown,\n  unknown,\n  unknown,\n  unknown,\n]\n\n/** Evaluate a whole calc-family expression to its dimension (or never). */\ntype EvalTop<S extends string> =\n  Trim<S> extends `${infer Name}(${infer Args})`\n    ? EvalFn<Trim<Name>, Args, Depth8>\n    : never\n\n// `never extends X` is vacuously true, so a plain `extends Dimension` check\n// would WRONGLY accept failed evaluations. Test for `never` explicitly.\ntype IsNever<T> = [T] extends [never] ? true : false\n\n// =====================================================================\n// 2. STRICT VALIDATOR + CALL-SITE HELPER\n// =====================================================================\n\n/**\n * Strict literal validator. Resolves to `S` when the expression is a\n * dimensionally-valid CSS math value, `never` otherwise.\n *\n * @example\n * type A = CalcLiteral<\"calc(10px + 2rem)\">     // \"calc(10px + 2rem)\"\n * type B = CalcLiteral<\"calc(10px + 45deg)\">    // never (length ≠ angle)\n * type C = CalcLiteral<\"clamp(1rem, 2vw, 3rem)\">// \"clamp(1rem, 2vw, 3rem)\"\n */\nexport type CalcLiteral<S extends string> =\n  IsNever<EvalTop<Trim<S>>> extends true ? never : S\n\n/**\n * Call-site validator helper. Mirrors `color()` / `easing()`.\n * Out-of-dimension expressions become a type error at the argument.\n */\nexport const cssCalc = <S extends string>(value: S & CalcLiteral<S>): S => value\n\n// =====================================================================\n// 3. SUGGESTION STRINGS — IntelliSense + onChange return types\n// =====================================================================\n\nexport type CalcFunctionName = \"calc\" | \"clamp\" | \"min\" | \"max\"\n\n/** Suggestion union — \"this is a calc-family string\". */\nexport type CalcString =\n  | `calc(${string})`\n  | `clamp(${string})`\n  | `min(${string})`\n  | `max(${string})`\n\n/** Function → output-string map. Used by the `fn?` prop to narrow onChange. */\nexport interface CalcStringMap {\n  calc: `calc(${string})`\n  clamp: `clamp(${string})`\n  min: `min(${string})`\n  max: `max(${string})`\n}\n\nexport type CalcFn = keyof CalcStringMap\n\n// =====================================================================\n// 4. UTILITY TYPES — operate on calc literals at the type level\n// =====================================================================\n\n/**\n * The CSS function family of a literal.\n *\n * @example\n * type T = FunctionOf<\"clamp(1rem, 2vw, 3rem)\">  // \"clamp\"\n */\nexport type FunctionOf<S extends string> =\n  Trim<S> extends `calc(${string}`\n    ? \"calc\"\n    : Trim<S> extends `clamp(${string}`\n      ? \"clamp\"\n      : Trim<S> extends `min(${string}`\n        ? \"min\"\n        : Trim<S> extends `max(${string}`\n          ? \"max\"\n          : never\n\n/**\n * The resolved dimension of a calc literal, or `never` if invalid.\n *\n * @example\n * type T = DimensionOfCalc<\"calc(10px + 2rem)\">  // \"length\"\n */\nexport type DimensionOfCalc<S extends string> =\n  EvalTop<Trim<S>> extends infer D extends Dimension ? D : never\n\n/** Length of a tuple — counts the outer function's comma args. */\ntype TupleLength<T extends readonly unknown[]> = T[\"length\"]\n\n/**\n * Argument count of the outer function (clamp ⇒ 3, min(a,b) ⇒ 2, calc ⇒ 1).\n */\nexport type ArgCountOf<S extends string> =\n  Trim<S> extends `${string}(${infer Args})`\n    ? TupleLength<SplitByComma<Args>>\n    : never\n\n// =====================================================================\n// 5. INTERNAL STATE — parse-tree discriminated union (exported)\n//\n// The editor's React state is the raw expression text (a calc string is\n// its own best serialization). `CalcNode` is the parse OUTPUT — exported\n// for advanced consumers (custom serialization, programmatic build).\n// =====================================================================\n\nexport type CalcNode =\n  | { kind: \"literal\"; value: string; dimension: Dimension | null }\n  | { kind: \"var\"; name: string; raw: string }\n  | {\n      kind: \"binary\"\n      op: \"+\" | \"-\" | \"*\" | \"/\"\n      left: CalcNode\n      right: CalcNode\n    }\n  | { kind: \"fn\"; name: CalcFunctionName; args: CalcNode[] }\n  | { kind: \"group\"; inner: CalcNode }\n\n// Re-export the kit's Dimension for convenience (consumers need it for CalcNode).\nexport type { Dimension } from \"@/lib/ridiculous-type-kit\"\n",
      "type": "registry:ui",
      "target": "components/ui/calc-editor/calc-editor.types.ts"
    },
    {
      "path": "src/components/ui/calc-editor/calc-editor.helpers.ts",
      "content": "// =====================================================================\n// calc-editor.helpers.ts\n//\n// Pure runtime parse / evaluate / format for CSS math expressions.\n// This is the SUPERSET of the type tier: it does operator precedence,\n// numeric value computation, var() tolerance, and friendly errors that\n// the compile-time validator (calc-editor.types.ts) cannot.\n// =====================================================================\n\nimport type {\n  CalcFunctionName,\n  CalcNode,\n  CalcString,\n  Dimension,\n} from \"./calc-editor.types\"\n\nconst CALC_FUNCTIONS: ReadonlySet<string> = new Set([\n  \"calc\",\n  \"clamp\",\n  \"min\",\n  \"max\",\n])\n\nconst MATH_CONSTANTS: ReadonlySet<string> = new Set([\n  \"pi\",\n  \"e\",\n  \"infinity\",\n  \"-infinity\",\n  \"nan\",\n])\n\n// ---------------------------------------------------------------------------\n// Tokenizer\n// ---------------------------------------------------------------------------\n\nexport type TokenType =\n  | \"number\"\n  | \"ident\"\n  | \"op\"\n  | \"lparen\"\n  | \"rparen\"\n  | \"comma\"\n\nexport interface Token {\n  type: TokenType\n  value: string\n}\n\nconst IDENT_START = /[a-zA-Z_-]/\nconst IDENT_CHAR = /[a-zA-Z0-9_-]/\nconst DIGIT = /[0-9]/\n\n/**\n * Lex a CSS math expression into tokens. Numbers carry their unit suffix\n * (`10px`, `50%`, `-5rem`); a leading sign is folded into the number when\n * it is in operand position (start, or right after `( , + - * /`).\n */\nexport function tokenizeCalc(src: string): Token[] {\n  const tokens: Token[] = []\n  let i = 0\n  const n = src.length\n\n  const prevMeaningful = (): Token | null =>\n    tokens.length > 0 ? tokens[tokens.length - 1] : null\n\n  // A sign is unary (part of a number) when nothing precedes it, or the\n  // previous token is an operator, a comma, or an opening paren.\n  const signIsUnary = (): boolean => {\n    const p = prevMeaningful()\n    return (\n      p === null || p.type === \"op\" || p.type === \"comma\" || p.type === \"lparen\"\n    )\n  }\n\n  while (i < n) {\n    const c = src[i]\n\n    // whitespace\n    if (c === \" \" || c === \"\\t\" || c === \"\\n\" || c === \"\\r\") {\n      i++\n      continue\n    }\n\n    if (c === \"(\") {\n      tokens.push({ type: \"lparen\", value: \"(\" })\n      i++\n      continue\n    }\n    if (c === \")\") {\n      tokens.push({ type: \"rparen\", value: \")\" })\n      i++\n      continue\n    }\n    if (c === \",\") {\n      tokens.push({ type: \"comma\", value: \",\" })\n      i++\n      continue\n    }\n\n    // CSS custom property `--name` (used inside var()) — an identifier, not\n    // two minus operators. Must precede the sign/number handling.\n    if (c === \"-\" && src[i + 1] === \"-\") {\n      let j = i\n      while (j < n && IDENT_CHAR.test(src[j])) j++\n      tokens.push({ type: \"ident\", value: src.slice(i, j) })\n      i = j\n      continue\n    }\n\n    // operator OR unary sign on a number\n    if (c === \"+\" || c === \"-\" || c === \"*\" || c === \"/\") {\n      const isSign = (c === \"+\" || c === \"-\") && signIsUnary()\n      if (!isSign) {\n        tokens.push({ type: \"op\", value: c })\n        i++\n        continue\n      }\n      // unary sign: fall through to number scanning starting at the sign\n    }\n\n    // number (optional sign, digits, optional fraction, optional unit/%)\n    if (\n      DIGIT.test(c) ||\n      c === \".\" ||\n      ((c === \"+\" || c === \"-\") && signIsUnary())\n    ) {\n      let j = i\n      if (src[j] === \"+\" || src[j] === \"-\") j++\n      let sawDigit = false\n      while (j < n && DIGIT.test(src[j])) {\n        j++\n        sawDigit = true\n      }\n      if (src[j] === \".\") {\n        j++\n        while (j < n && DIGIT.test(src[j])) {\n          j++\n          sawDigit = true\n        }\n      }\n      // scientific notation (e.g. 1e3) — only if a digit was seen\n      if (sawDigit && (src[j] === \"e\" || src[j] === \"E\")) {\n        let k = j + 1\n        if (src[k] === \"+\" || src[k] === \"-\") k++\n        if (k < n && DIGIT.test(src[k])) {\n          k++\n          while (k < n && DIGIT.test(src[k])) k++\n          j = k\n        }\n      }\n      if (!sawDigit) {\n        // a lone sign or dot — bail to operator handling for the sign\n        if (c === \"+\" || c === \"-\") {\n          tokens.push({ type: \"op\", value: c })\n          i++\n          continue\n        }\n        // a stray \".\" — emit as an unknown ident-ish token so parse fails\n        tokens.push({ type: \"ident\", value: \".\" })\n        i++\n        continue\n      }\n      // unit suffix or percent\n      let unit = \"\"\n      if (src[j] === \"%\") {\n        unit = \"%\"\n        j++\n      } else {\n        let u = j\n        while (u < n && IDENT_CHAR.test(src[u])) u++\n        unit = src.slice(j, u)\n        j = u\n      }\n      tokens.push({\n        type: \"number\",\n        value: src.slice(i, j - unit.length) + unit,\n      })\n      i = j\n      continue\n    }\n\n    // identifier (function name, var, constant, custom property after var()\n    if (IDENT_START.test(c)) {\n      let j = i\n      while (j < n && IDENT_CHAR.test(src[j])) j++\n      tokens.push({ type: \"ident\", value: src.slice(i, j) })\n      i = j\n      continue\n    }\n\n    // anything else is unrecognized — emit as ident so the parser rejects it\n    tokens.push({ type: \"ident\", value: c })\n    i++\n  }\n\n  return tokens\n}\n\n// ---------------------------------------------------------------------------\n// Parser — recursive descent with precedence\n// ---------------------------------------------------------------------------\n\ninterface ParseError {\n  message: string\n}\n\nclass Parser {\n  private pos = 0\n  error: ParseError | null = null\n\n  constructor(private readonly tokens: Token[]) {}\n\n  private peek(): Token | null {\n    return this.pos < this.tokens.length ? this.tokens[this.pos] : null\n  }\n\n  private next(): Token | null {\n    return this.pos < this.tokens.length ? this.tokens[this.pos++] : null\n  }\n\n  private fail(message: string): null {\n    if (!this.error) this.error = { message }\n    return null\n  }\n\n  atEnd(): boolean {\n    return this.pos >= this.tokens.length\n  }\n\n  /** Entry: the whole source must be a single calc-family function call. */\n  parseRoot(): CalcNode | null {\n    const node = this.parseFactor()\n    if (node === null) return null\n    if (!this.atEnd()) return this.fail(\"unexpected trailing tokens\")\n    if (node.kind !== \"fn\") return this.fail(\"expected a calc-family function\")\n    return node\n  }\n\n  // expr := term (('+' | '-') term)*   (left-associative)\n  private parseExpr(): CalcNode | null {\n    let left = this.parseTerm()\n    if (left === null) return null\n    for (;;) {\n      const t = this.peek()\n      if (t && t.type === \"op\" && (t.value === \"+\" || t.value === \"-\")) {\n        this.next()\n        const right = this.parseTerm()\n        if (right === null) return this.fail(\"expected operand after operator\")\n        left = { kind: \"binary\", op: t.value as \"+\" | \"-\", left, right }\n      } else {\n        break\n      }\n    }\n    return left\n  }\n\n  // term := factor (('*' | '/') factor)*   (left-associative, tighter)\n  private parseTerm(): CalcNode | null {\n    let left = this.parseFactor()\n    if (left === null) return null\n    for (;;) {\n      const t = this.peek()\n      if (t && t.type === \"op\" && (t.value === \"*\" || t.value === \"/\")) {\n        this.next()\n        const right = this.parseFactor()\n        if (right === null) return this.fail(\"expected operand after operator\")\n        left = { kind: \"binary\", op: t.value as \"*\" | \"/\", left, right }\n      } else {\n        break\n      }\n    }\n    return left\n  }\n\n  // factor := number | '(' expr ')' | name '(' args ')'\n  private parseFactor(): CalcNode | null {\n    const t = this.peek()\n    if (t === null) return this.fail(\"unexpected end of expression\")\n\n    if (t.type === \"number\") {\n      this.next()\n      return {\n        kind: \"literal\",\n        value: t.value,\n        dimension: dimensionOf(t.value),\n      }\n    }\n\n    if (t.type === \"ident\" && MATH_CONSTANTS.has(t.value.toLowerCase())) {\n      this.next()\n      return { kind: \"literal\", value: t.value, dimension: \"number\" }\n    }\n\n    if (t.type === \"lparen\") {\n      this.next()\n      const inner = this.parseExpr()\n      if (inner === null) return null\n      const close = this.next()\n      if (!close || close.type !== \"rparen\")\n        return this.fail(\"expected closing paren\")\n      return { kind: \"group\", inner }\n    }\n\n    if (t.type === \"ident\") {\n      // function call: ident '(' ... ')'\n      const after = this.tokens[this.pos + 1]\n      if (!after || after.type !== \"lparen\")\n        return this.fail(`unexpected identifier \"${t.value}\"`)\n      const name = t.value.toLowerCase()\n      this.next() // ident\n      this.next() // lparen\n\n      if (name === \"var\") {\n        return this.parseVarRest()\n      }\n      if (!CALC_FUNCTIONS.has(name)) {\n        return this.fail(`unsupported function \"${t.value}\"`)\n      }\n      return this.parseFnRest(name as CalcFunctionName)\n    }\n\n    return this.fail(`unexpected token \"${t.value}\"`)\n  }\n\n  // var(--name [, fallback]) — captured verbatim, treated as opaque.\n  private parseVarRest(): CalcNode | null {\n    const nameTok = this.next()\n    if (!nameTok || nameTok.type !== \"ident\" || !nameTok.value.startsWith(\"--\"))\n      return this.fail(\"var() expects a custom property name\")\n    const parts: string[] = [nameTok.value]\n    // consume optional fallback verbatim up to the matching close paren\n    let depth = 0\n    for (;;) {\n      const t = this.peek()\n      if (t === null) return this.fail(\"unterminated var()\")\n      if (t.type === \"rparen\" && depth === 0) {\n        this.next()\n        break\n      }\n      if (t.type === \"lparen\") depth++\n      if (t.type === \"rparen\") depth--\n      parts.push(t.value)\n      this.next()\n    }\n    const raw = `var(${parts.join(\" \")})`\n    return { kind: \"var\", name: nameTok.value, raw }\n  }\n\n  // Parse the arg list of a calc-family function, then the close paren.\n  private parseFnRest(name: CalcFunctionName): CalcNode | null {\n    const args: CalcNode[] = []\n\n    // calc(): single expression, no top-level commas.\n    if (name === \"calc\") {\n      if (this.peek()?.type === \"rparen\")\n        return this.fail(\"calc() requires an expression\")\n      const expr = this.parseExpr()\n      if (expr === null) return null\n      const close = this.next()\n      if (!close || close.type !== \"rparen\")\n        return this.fail(\"expected closing paren\")\n      args.push(expr)\n      return { kind: \"fn\", name, args }\n    }\n\n    // clamp/min/max: comma-separated expression list.\n    if (this.peek()?.type === \"rparen\")\n      return this.fail(`${name}() requires at least one argument`)\n    for (;;) {\n      const expr = this.parseExpr()\n      if (expr === null) return null\n      args.push(expr)\n      const t = this.peek()\n      if (t && t.type === \"comma\") {\n        this.next()\n        continue\n      }\n      break\n    }\n    const close = this.next()\n    if (!close || close.type !== \"rparen\")\n      return this.fail(\"expected closing paren\")\n\n    if (name === \"clamp\" && args.length !== 3)\n      return this.fail(\"clamp() requires exactly 3 arguments\")\n    if ((name === \"min\" || name === \"max\") && args.length < 1)\n      return this.fail(`${name}() requires at least one argument`)\n\n    return { kind: \"fn\", name, args }\n  }\n}\n\n/** Parse a CSS math expression into an AST, or `null` on any syntax error. */\nexport function parseCalc(src: string): CalcNode | null {\n  const trimmed = src.trim()\n  if (trimmed === \"\") return null\n  const parser = new Parser(tokenizeCalc(trimmed))\n  return parser.parseRoot()\n}\n\n// ---------------------------------------------------------------------------\n// Dimension analysis (runtime mirror of the type evaluator, var()-tolerant)\n// ---------------------------------------------------------------------------\n\nconst LENGTH_UNITS: ReadonlySet<string> = new Set([\n  \"cqmin\",\n  \"cqmax\",\n  \"vmin\",\n  \"vmax\",\n  \"svw\",\n  \"svh\",\n  \"svi\",\n  \"svb\",\n  \"lvw\",\n  \"lvh\",\n  \"lvi\",\n  \"lvb\",\n  \"dvw\",\n  \"dvh\",\n  \"dvi\",\n  \"dvb\",\n  \"cqw\",\n  \"cqh\",\n  \"cqi\",\n  \"cqb\",\n  \"rlh\",\n  \"rem\",\n  \"cap\",\n  \"rex\",\n  \"px\",\n  \"em\",\n  \"ex\",\n  \"ch\",\n  \"ic\",\n  \"lh\",\n  \"vw\",\n  \"vh\",\n  \"vi\",\n  \"vb\",\n  \"cm\",\n  \"mm\",\n  \"in\",\n  \"pt\",\n  \"pc\",\n  \"q\",\n])\nconst ANGLE_UNITS: ReadonlySet<string> = new Set([\"deg\", \"grad\", \"rad\", \"turn\"])\nconst TIME_UNITS: ReadonlySet<string> = new Set([\"s\", \"ms\"])\nconst RESOLUTION_UNITS: ReadonlySet<string> = new Set([\n  \"dpi\",\n  \"dpcm\",\n  \"dppx\",\n  \"x\",\n])\n\nconst NUMBER_RE = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?$/\n\n/** Classify a single value literal (e.g. \"10px\", \"50%\", \"-2\") to a Dimension. */\nexport function dimensionOf(value: string): Dimension | null {\n  const v = value.trim()\n  if (v === \"\") return null\n  if (v.endsWith(\"%\")) {\n    return NUMBER_RE.test(v.slice(0, -1)) ? \"percent\" : null\n  }\n  const m = v.match(/^([+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?)([a-zA-Z]+)$/)\n  if (m) {\n    const unit = m[2].toLowerCase()\n    if (LENGTH_UNITS.has(unit)) return \"length\"\n    if (ANGLE_UNITS.has(unit)) return \"angle\"\n    if (TIME_UNITS.has(unit)) return \"time\"\n    if (RESOLUTION_UNITS.has(unit)) return \"resolution\"\n    if (unit === \"fr\") return \"flex\"\n    return null\n  }\n  if (NUMBER_RE.test(v)) return \"number\"\n  return null\n}\n\n// A var()/opaque operand is dimension-agnostic: represented as `undefined`\n// during folding so it adopts a concrete sibling dimension.\ntype DimOrAgnostic = Dimension | \"agnostic\"\n\nfunction combineDim(\n  a: DimOrAgnostic,\n  op: \"+\" | \"-\" | \"*\" | \"/\",\n  b: DimOrAgnostic,\n): DimOrAgnostic | null {\n  if (op === \"+\" || op === \"-\") {\n    if (a === \"agnostic\") return b\n    if (b === \"agnostic\") return a\n    return a === b ? a : null\n  }\n  if (op === \"*\") {\n    if (a === \"agnostic\") return b === \"agnostic\" ? \"agnostic\" : b\n    if (b === \"agnostic\") return a\n    if (a === \"number\") return b\n    if (b === \"number\") return a\n    return null\n  }\n  // \"/\"\n  if (b === \"agnostic\") return a\n  if (a === \"agnostic\") return \"agnostic\"\n  return b === \"number\" ? a : null\n}\n\nfunction dimOfNode(node: CalcNode): DimOrAgnostic | null {\n  switch (node.kind) {\n    case \"literal\":\n      return node.dimension\n    case \"var\":\n      return \"agnostic\"\n    case \"group\":\n      return dimOfNode(node.inner)\n    case \"binary\": {\n      const a = dimOfNode(node.left)\n      if (a === null) return null\n      const b = dimOfNode(node.right)\n      if (b === null) return null\n      return combineDim(a, node.op, b)\n    }\n    case \"fn\": {\n      if (node.name === \"calc\") return dimOfNode(node.args[0])\n      // clamp/min/max: all args must agree (agnostic adopts the concrete one)\n      let acc: DimOrAgnostic | null = null\n      for (const arg of node.args) {\n        const d = dimOfNode(arg)\n        if (d === null) return null\n        if (acc === null || acc === \"agnostic\") {\n          acc = d\n        } else if (d !== \"agnostic\" && d !== acc) {\n          return null\n        }\n      }\n      return acc\n    }\n  }\n}\n\n/** The dimension a parsed expression resolves to, or `null` on a violation. */\nexport function calcDimension(node: CalcNode): Dimension | null {\n  const d = dimOfNode(node)\n  if (d === null) return null\n  if (d === \"agnostic\") return \"number\" // wholly-opaque expr → number-tolerant\n  return d\n}\n\n// ---------------------------------------------------------------------------\n// Facade\n// ---------------------------------------------------------------------------\n\nexport interface EvaluateResult {\n  node: CalcNode | null\n  dimension: Dimension | null\n  error: string | null\n}\n\n/** Parse + dimension-check in one call. UI-friendly. */\nexport function evaluateCalc(src: string): EvaluateResult {\n  const node = parseCalc(src)\n  if (node === null) {\n    return { node: null, dimension: null, error: \"Invalid CSS math syntax\" }\n  }\n  const dimension = calcDimension(node)\n  if (dimension === null) {\n    return {\n      node,\n      dimension: null,\n      error: \"Incompatible dimensions (unit mismatch)\",\n    }\n  }\n  return { node, dimension, error: null }\n}\n\n// ---------------------------------------------------------------------------\n// computeCalc — numeric evaluation where units are resolvable\n// ---------------------------------------------------------------------------\n\nexport interface ComputeContext {\n  /** Viewport width in px — resolves vw/vmin/etc. Required. */\n  viewport: number\n  /** Viewport height in px — resolves vh. Defaults to `viewport`. */\n  viewportHeight?: number\n  /** Root font size in px — resolves rem. Defaults to 16. */\n  rootFontSize?: number\n  /** Element font size in px — resolves em. Defaults to `rootFontSize`. */\n  fontSize?: number\n  /** The basis a percentage resolves against. If omitted, `%` blocks compute. */\n  basis?: number\n}\n\n// Resolve a single value literal to px (or its raw number for unitless),\n// or null when it cannot be resolved in this context.\nfunction resolveLiteral(value: string, ctx: ComputeContext): number | null {\n  const v = value.trim()\n  const root = ctx.rootFontSize ?? 16\n  const fontSize = ctx.fontSize ?? root\n  const vw = ctx.viewport / 100\n  const vh = (ctx.viewportHeight ?? ctx.viewport) / 100\n\n  if (v.endsWith(\"%\")) {\n    const num = Number(v.slice(0, -1))\n    if (!Number.isFinite(num)) return null\n    return ctx.basis === undefined ? null : (num / 100) * ctx.basis\n  }\n\n  const m = v.match(/^([+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?)([a-zA-Z]*)$/)\n  if (!m) {\n    const lower = v.toLowerCase()\n    if (lower === \"pi\") return Math.PI\n    if (lower === \"e\") return Math.E\n    if (lower === \"infinity\") return Number.POSITIVE_INFINITY\n    if (lower === \"-infinity\") return Number.NEGATIVE_INFINITY\n    if (lower === \"nan\") return Number.NaN\n    return null\n  }\n  const num = Number(m[1])\n  if (!Number.isFinite(num) && m[2] !== \"\") return null\n  const unit = m[2].toLowerCase()\n\n  switch (unit) {\n    case \"\":\n      return num\n    case \"px\":\n      return num\n    case \"rem\":\n    case \"rlh\":\n      return num * root\n    case \"em\":\n    case \"lh\":\n      return num * fontSize\n    case \"vw\":\n    case \"vi\":\n      return num * vw\n    case \"vh\":\n    case \"vb\":\n      return num * vh\n    case \"vmin\":\n      return num * Math.min(vw, vh)\n    case \"vmax\":\n      return num * Math.max(vw, vh)\n    case \"cm\":\n      return num * 96 * (1 / 2.54)\n    case \"mm\":\n      return num * 96 * (1 / 25.4)\n    case \"in\":\n      return num * 96\n    case \"pt\":\n      return num * (96 / 72)\n    case \"pc\":\n      return num * 16\n    case \"q\":\n      return num * 96 * (1 / 25.4) * 0.25\n    default:\n      // Unit we don't numerically resolve (deg/s/etc.) — treat as a raw number\n      // for value purposes; dimensional validity is checked separately.\n      return num\n  }\n}\n\n/**\n * Numerically evaluate an expression to a px value (or unitless number),\n * resolving units against `ctx`. Returns `null` when a `var()` or an\n * unresolvable `%` blocks the computation.\n */\nexport function computeCalc(\n  node: CalcNode,\n  ctx: ComputeContext,\n): number | null {\n  switch (node.kind) {\n    case \"literal\":\n      return resolveLiteral(node.value, ctx)\n    case \"var\":\n      return null // opaque — cannot compute\n    case \"group\":\n      return computeCalc(node.inner, ctx)\n    case \"binary\": {\n      const a = computeCalc(node.left, ctx)\n      if (a === null) return null\n      const b = computeCalc(node.right, ctx)\n      if (b === null) return null\n      switch (node.op) {\n        case \"+\":\n          return a + b\n        case \"-\":\n          return a - b\n        case \"*\":\n          return a * b\n        case \"/\":\n          return b === 0 ? null : a / b\n      }\n      return null\n    }\n    case \"fn\": {\n      const vals: number[] = []\n      for (const arg of node.args) {\n        const v = computeCalc(arg, ctx)\n        if (v === null) return null\n        vals.push(v)\n      }\n      switch (node.name) {\n        case \"calc\":\n          return vals[0]\n        case \"min\":\n          return Math.min(...vals)\n        case \"max\":\n          return Math.max(...vals)\n        case \"clamp\": {\n          const [lo, pref, hi] = vals\n          return Math.min(Math.max(pref, lo), hi)\n        }\n      }\n      return null\n    }\n  }\n}\n\n// ---------------------------------------------------------------------------\n// formatCalc — canonical serialization\n// ---------------------------------------------------------------------------\n\nfunction formatNode(node: CalcNode): string {\n  switch (node.kind) {\n    case \"literal\":\n      return node.value\n    case \"var\":\n      return node.raw\n    case \"group\":\n      return `(${formatNode(node.inner)})`\n    case \"binary\":\n      return `${formatNode(node.left)} ${node.op} ${formatNode(node.right)}`\n    case \"fn\":\n      return `${node.name}(${node.args.map(formatNode).join(\", \")})`\n  }\n}\n\n/** Re-serialize a parsed expression with canonical spacing. */\nexport function formatCalc(node: CalcNode): CalcString {\n  return formatNode(node) as CalcString\n}\n\nexport type { CalcString }\n",
      "type": "registry:ui",
      "target": "components/ui/calc-editor/calc-editor.helpers.ts"
    }
  ],
  "type": "registry:ui"
}