Repository navigation
FormulaInput
FormulaInput is a billing-grade formula editor that combines Monaco Editor with
mathjs BigNumber evaluation. It is designed for authoring formulas such as
rate * hours + bonus that are stored in a database and re-evaluated by a Java backend.
Key characteristics:
-
Live evaluation — the formula is parsed and evaluated on every keystroke via mathjs
in 64-digit BigNumber mode. The result appears inline (as a ghost-text decoration) and,
optionally, in a
ReadonlyInputbelow the editor. - Variable-aware autocomplete — both fixed (API-sourced) and dynamic (user-defined) parameters are registered as Monaco completions and hover providers.
-
Precision drift detection — if the BigNumber result deviates from a JS
numberresult beyond a configurable threshold, a warning is shown to signal a potential backend disagreement. -
Carbon Design System styling — the editor is wrapped in a container styled to match
Carbon's
TextInput, including focus rings, error/warning border colours, andhelper-text-01typography for validation messages. -
Native form integration — an optional
nameprop renders a hidden<input type="hidden">so the formula participates inFormDataon form submission.
FormulaInput (index.tsx)
├── useFormulaEngine — evaluation, variable resolution, precision check
│ └── utils.ts — pure helpers: parseFormula, evaluateFormula,
│ extractVariables, checkDoublePrecisionDrift
├── useMonacoFormula — Monaco language registration, completions,
│ hover providers, squiggle markers
├── DependencyGraph — visualises which variables the formula uses
│ (Carbon Tag graph)
├── VariablePanel — lists all available variables (fixed / dynamic)
│ with used/unused opacity differentiation
└── ReadonlyInput — displays the evaluated numeric result
fixedParams + dynamicParams
│
▼
useFormulaEngine ──────► mergedScope ──► useMonacoFormula
│
├── formula (string)
├── result { value, error, raw }
├── usedVariables (string[])
└── precisionWarning (string | null)
│
▼
FormulaInput JSX
├── <Editor> (Monaco, FORMULA_LANGUAGE_ID)
├── helper-text div (error / warning)
├── <ReadonlyInput> (evaluated result)
├── <DependencyGraph>
└── <VariablePanel × 2> (fixed + dynamic)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
fixedParams |
Record<string, number> |
✅ | — | API-sourced parameters (e.g. from TanStack Query). Take precedence over dynamicParams on name collision — server values always win. |
dynamicParams |
Record<string, number> |
✅ | — | Runtime-mutable parameters added or removed by the user upstream. Immediately available in autocomplete. |
id |
string |
❌ | React useId()
|
Stable HTML id prefix. Derived ids ${id}-formula-label and ${id}-evaluated-result are applied to internal elements. Falls back to a React-generated uid so multiple instances on the same page remain accessible. |
ariaLabel |
string |
❌ | 'Formula editor' |
Accessible label for the <section> landmark. Pass distinct values when multiple instances appear on the same page. |
initialFormula |
string |
❌ | '' |
Starting formula on first mount. Non-reactive — changes after mount have no effect. Remount with a new key to reset. |
onChange |
(formula: string) => void |
❌ | — | Called on every formula change. The raw string is passed through without sanitisation; the consumer is responsible for persisting. |
displayResult |
boolean |
❌ | true |
When false, hides the ReadonlyInput below the editor. Errors and precision warnings are always shown regardless of this flag. |
displayDependencyGraph |
boolean |
❌ | true |
When false, hides the dependency graph and variable panels. Suppresses the five related useMemo computations. |
readOnly |
boolean |
❌ | false |
Renders the editor in read-only mode. All editing interactions are disabled; selection and copy still work. |
name |
string |
❌ | — | HTML name for native form integration. Renders a hidden <input type="hidden"> carrying the current formula so it is included in FormData. |
Manages the formula lifecycle: state, parsing, evaluation, variable extraction, and
precision drift detection. The formula string is parsed once per keystroke into a
shared AST; both result and usedVariables derive from that single node.
const { formula, result, usedVariables, precisionWarning, mergedScope, setFormula } =
useFormulaEngine({ fixedParams, dynamicParams, initialFormula, onChange });Returns a FormulaEngineState object.
Registers the custom FORMULA_LANGUAGE_ID Monaco language (completions, hover docs,
squiggle markers) and returns the onMount handler that activates it. Depends on
allVariables (merged scope), the current formula, evaluationResult, evaluationError,
and the active Carbon theme.
A graphical component (Carbon Tags) that highlights which variables from mergedScope
appear in the current formula. Variables not referenced are rendered at reduced opacity.
Renders an accessible list of parameter name / value pairs as Carbon Tags. Used twice:
once for fixed params (blue) and once for dynamic params (teal). Tags in usedSet are
rendered at full opacity.
import { FormulaInput } from '@/components/Form/FormulaInput';
function BillingForm() {
const [formula, setFormula] = React.useState('');
return (
<FormulaInput
id="billing-formula"
ariaLabel="Billing rate formula"
fixedParams={{ tax_rate: 0.15, base_price: 100 }}
dynamicParams={{ quantity: 5, discount: 0.1 }}
initialFormula="base_price * quantity * (1 - discount) * (1 + tax_rate)"
onChange={setFormula}
/>
);
}The name prop adds a hidden input so the formula string is submitted with FormData:
<form onSubmit={(e) => {
const data = new FormData(e.currentTarget);
console.log(data.get('rate_formula')); // current formula string
}}>
<FormulaInput
name="rate_formula"
fixedParams={{ hourly_rate: 45 }}
dynamicParams={{ hours: 40 }}
/>
<button type="submit">Save</button>
</form>Always supply distinct id and ariaLabel props so landmark navigation and derived element
ids (${id}-formula-label, ${id}-evaluated-result) remain unique:
<FormulaInput
id="rate-calc"
ariaLabel="Rate formula"
fixedParams={rateParams}
dynamicParams={dynamicRateParams}
onChange={setRateFormula}
/>
<FormulaInput
id="discount-calc"
ariaLabel="Discount formula"
fixedParams={discountParams}
dynamicParams={dynamicDiscountParams}
onChange={setDiscountFormula}
/>Use readOnly to display a stored formula without allowing editing:
<FormulaInput
id="saved-formula"
ariaLabel="Saved billing formula (read-only)"
fixedParams={savedFixedParams}
dynamicParams={{}}
initialFormula={storedFormulaFromDB}
readOnly
displayDependencyGraph={false}
/>When a formula produces a result that differs between BigNumber and JS number arithmetic
(e.g. chained floating-point multiplications), a warning is rendered below the editor:
⚠ Precision loss detected: relative difference 2.3e-12. The backend result may differ
from what is shown here.
This warning is surfaced automatically — no consumer code is required. It is a hint that the formula is sensitive to floating-point representation on the Java backend.
If the formula references a variable not present in either fixedParams or dynamicParams,
the engine returns an error and the editor displays a red squiggle on the unknown token:
Formula: rate * hours + bonus
Error: Undefined symbol: bonus
The helper text below the editor shows the error message, and the editor border turns red
(Carbon $support-error).
-
initialFormulais non-reactive. The prop is consumed once on mount viauseState. Changing it after mount has no effect. Remount the component with a different Reactkeyto force a reset. -
The visible "Formula" label does not focus Monaco on click.
htmlForcannot target Monaco's auto-generated internal textarea id, so the<label>element is visually connected viaaria-labelledbyon the wrapper div but does not receive native focus-forwarding on click. -
Monaco's internal
ariaLabeloption is hardcoded to'Formula expression'and does not derive from theariaLabelprop. Screen readers will announce'Formula expression'for the editor widget regardless of whatariaLabelis passed to the component. TheariaLabelprop controls only the outer<section>landmark. -
Horizontal overflow is not scrollable.
wordWrap: 'off'andscrollbar: { horizontal: 'hidden' }are set by default. Long formulas that exceed the editor width are clipped; users must navigate with arrow keys. -
No maximum editor height. The editor grows to fit multi-line content without an upper bound. Layout shift may occur when long formulas are pasted.
-
BigNumber precision is 64 digits. Results beyond this precision will round. The precision-drift warning compares the BigNumber result against a standard JS
number(IEEE 754 double) result to surface the most common disagreement with the Java backend.
- Frontend Architecture Overview — references the Form component layer
-
ReadonlyInput —
ReadonlyInputis rendered insideFormulaInputfor the evaluated result - Components — component index table
- ReadonlyInput — the read-only result display used internally
-
Contexts and Hooks — useTheme —
the
useThemehook drives Monaco's colour scheme -
React Query —
fixedParamsare typically populated from a TanStack Query result - Source files:
This wiki documents NR Waste Plus for users and developers. Verify security and deployment guidance against the current application configuration before applying it elsewhere.
- Report an issue or ask a question in the GitHub repository.
- Follow the Contributing Guidelines to propose an update.
- Read the Code of Conduct.
Collection indexes support human browsing and machine retrieval. Keep both current when adding or moving wiki pages.
NR Waste Plus Wiki | GitHub Repository