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

OptionTypeDescription
getEditor() => HTMLElement | nullReturns the editor element (not needed if passing ref)
autoAttachbooleanAuto-attach input/keydown listeners (default: true when using ref)
highlightboolean | { color?, highlightName? }Enable CSS Custom Highlight API for expression highlighting
onApply(result, expression) => voidCalled when result is applied
onBeforeApply(context) => voidIntercept apply to handle replacement yourself
onDismiss(expression) => voidCalled when suggestion is dismissed
getPosition(rect: DOMRect) => { top, left }Custom tooltip positioning
formatResult(result: number) => stringCustom 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");
// => null

Core 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);
// => 200

Rich 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);
    },
  });
}