Overview
Inline math calculation for React.
Installation
Demo
Type an expression, see the result. Tab to apply, Space to dismiss.
Features
One import, three lines. Auto-attaches listeners, built-in animation.
import { useInlineCalc } from "react-inline-calc";
const ref = useRef<HTMLDivElement>(null);
const { Tooltip } = useInlineCalc(ref, { highlight: true });
<div ref={ref} contentEditable />
<Tooltip />API
useInlineCalc(ref, options?)
Headless hook for inline math calculation. Auto-attaches listeners when passing a ref, or use manual mode with getEditor.
// Simple: pass ref directly
const { Tooltip, show } = useInlineCalc(editorRef);
// With options
const { Tooltip, show } = useInlineCalc(editorRef, { highlight: true, onApply: ... });
// Manual mode: pass options with getEditor
const inlineCalc = useInlineCalc({ getEditor: () => editorRef.current });Options
| Option | Type | Description |
|---|---|---|
| getEditor | () => HTMLElement | null | Returns the editor element (not needed if passing ref) |
| autoAttach | boolean | Auto-attach input/keydown listeners (default: true when using ref) |
| highlight | boolean | { color?, highlightName? } | Enable CSS Custom Highlight API for expression highlighting |
| onApply | (result, expression) => void | Called when result is applied |
| onBeforeApply | (context) => void | Intercept apply to handle replacement yourself |
| onDismiss | (expression) => void | Called when suggestion is dismissed |
| getPosition | (rect: DOMRect) => { top, left } | Custom tooltip positioning |
| formatResult | (result: number) => string | Custom result formatting (default: toLocaleString) |
Returns
interface InlineCalcReturn {
// State
expression: string | null; // Detected expression (e.g., "100+50")
result: number | null; // Calculated result (e.g., 150)
show: boolean; // Whether to show the tooltip
position: { top, left }; // Tooltip position (viewport coords)
// Bound component
Tooltip: React.FC; // Pre-bound tooltip (knows result + position)
tooltipProps: { ... }; // Or spread these on InlineCalcTooltip manually
// Actions
apply: () => void; // Apply result (replace expression)
dismiss: () => void; // Dismiss (won't re-suggest)
clear: () => void; // Clear (will re-suggest)
// Handlers (only needed in manual mode)
handleKeyDown: (e) => boolean;
handleInput: (text, textNodes?, cursorPosition?) => boolean;
}InlineCalcTooltip
Default tooltip component. Optional — use your own UI with the headless hook.
<InlineCalcTooltip
result={150}
position={{ top: 100, left: 200 }}
show={true}
keyLabel="Tab"
formatResult={(n) => n.toFixed(2)}
portal={true}
// For animations (e.g., framer-motion)
as={motion.div}
animationProps={{
initial: { opacity: 0 },
animate: { opacity: 1 },
}}
/>detectMathExpression(text)
Pure utility function — use it anywhere, no React required. Supports + - * × / with decimals and commas.
import { detectMathExpression } from "react-inline-calc";
detectMathExpression("Total: 100+50 items");
// => { expression: "100+50", result: 150, startIndex: 7, endIndex: 13 }
detectMathExpression("3.14 × 2");
// => { expression: "3.14 × 2", result: 6.28, startIndex: 0, endIndex: 8 }
detectMathExpression("No math here");
// => nullCore Functions (Non-React)
For non-React projects or custom integrations, import from react-inline-calc/core.
import { detectMathExpression, tokenize, evaluateTokens } from "react-inline-calc/core";
// Detect and evaluate in one step
const result = detectMathExpression("100+50*2");
// => { expression: "100+50*2", result: 200, startIndex: 0, endIndex: 8 }
// Or tokenize and evaluate separately for more control
const tokens = tokenize("100+50*2");
// => [100, "+", 50, "*", 2]
const value = evaluateTokens(tokens);
// => 200Rich Text Editors
Use onBeforeApply to handle text replacement with the editor's native API instead of DOM manipulation.
import { useEditor } from "@tiptap/react";
import { useInlineCalc } from "react-inline-calc";
function TipTapEditor() {
const editor = useEditor({ extensions: [StarterKit] });
const { Tooltip, show } = useInlineCalc({
getEditor: () => document.querySelector(".ProseMirror"),
onBeforeApply: ({ expression, formattedResult, preventDefault }) => {
preventDefault(); // Skip default DOM mutation
editor?.commands.insertContent(formattedResult);
},
});
}