GitHub ↗

formula

Zero-dependency spreadsheet formula parser and evaluator.


A pure-TypeScript engine that turns a =-prefixed string into a computed value, resolving cell references through a callback you provide. It has no dependencies and no opinion about where your grid lives — it powers the bulk-editor's formulas but works standalone.

Installation

pnpm add @fluixi/formula

Usage

import { evaluateFormula } from '@fluixi/formula';

const grid: Record<string, string> = { A1: '2', A2: '3', A3: '=A1+A2' };
const resolve = (addr: string) => grid[addr] ?? '';

evaluateFormula('=SUM(A1:A3)', { resolve }); // "10"
evaluateFormula('=IF(A1>A2,"hi","lo")', { resolve }); // "lo"
evaluateFormula('plain text', { resolve }); // "plain text" — unchanged

Input that doesn't start with = is returned unchanged, so evaluateFormula drops straight into anywhere expecting an identity value transform.

Supported syntax

  • Operators+ - * / ^, string concat &, comparisons = <> < > <= >=, unary + / -, postfix %. Power is right-associative.
  • References — A1 addresses (B3) and ranges (A1:B3), resolved through resolve. Ranges expand to individual cells.
  • Literals — numbers, "double-quoted strings" ("" escapes a quote), TRUE / FALSE.
  • FunctionsSUM, AVERAGE/AVG, MIN, MAX, COUNT, COUNTA, PRODUCT, ROUND, ABS, SQRT, FLOOR, CEILING, INT, MOD, POWER, IF, AND, OR, NOT, CONCAT/CONCATENATE, LEN, UPPER, LOWER, TRIM. Names are case-insensitive.

Errors

Problems surface as spreadsheet-style strings rather than exceptions, and propagate through operators and across cell references:

Error Meaning
#DIV/0! Division by zero.
#NAME? Unknown function.
#VALUE! A value couldn't be coerced (e.g. text used as a number).
#NUM! Numeric overflow / invalid result.
#CYCLE! Circular reference between cells.
#ERROR! Syntax error.

A referenced cell that already holds an error passes it along, so an error shows at its source and everywhere downstream.

Custom functions

Pass extra functions (merged over the built-ins, matched case-insensitively). Each receives already-evaluated arguments; a range arrives as a RangeValue.

evaluateFormula('=DOUBLE(21)', {
  resolve,
  functions: { double: (args) => (typeof args[0] === 'number' ? args[0] * 2 : 0) },
}); // "42"

With bulk-editor

The bulk-editor ships a one-line adapter, formulaEvaluator(), that wires this engine into its evaluate hook — the editor's cell resolver handles A1 addressing and circular-reference detection for you:

import { BulkEditor, formulaEvaluator } from '@fluixi/bulk-editor';

<BulkEditor columns={columns} rows={rows} evaluate={formulaEvaluator()} />

API

Beyond evaluateFormula, the package exports its building blocks: parse (string → AST), tokenize, the A1 helpers (colToLabel, labelToCol, parseAddress, expandRange), the BUILTINS registry, and the value helpers (ERR, isError, isRange, makeError).


Part of the Fluixi UI component library. Made with ☕ by the Fluixi team.