npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-inline-calc

v0.1.0

Published

Inline math calculation with Tab-to-complete for React inputs

Readme

react-inline-calc

Inline math calculation with Tab-to-complete for React. Type 9439+3299, see = 12,738, press Tab to replace.

https://github.com/user/react-inline-calc/assets/demo.gif

Features

  • Detects math as you type — addition, subtraction, multiplication, division
  • Tab to apply — replaces expression with result
  • Escape to dismiss — won't re-suggest until you change the expression
  • Headless hook — bring your own UI, or use the default toast
  • Tiny — ~2KB minified
  • TypeScript — full type definitions

Install

npm install react-inline-calc

Quick Start

import { useInlineCalc, InlineCalcToast, getTextNodes } from "react-inline-calc";
import { useRef } from "react";

function Editor() {
  const editorRef = useRef<HTMLDivElement>(null);
  const inlineCalc = useInlineCalc(editorRef); // Simple: just pass the ref

  const handleInput = () => {
    const text = editorRef.current?.textContent || "";
    inlineCalc.handleInput(text, getTextNodes(editorRef.current));
  };

  return (
    <>
      <div
        ref={editorRef}
        contentEditable
        onInput={handleInput}
        onKeyDown={(e) => {
          if (inlineCalc.handleKeyDown(e)) return;
          // ... other handlers
        }}
        style={{ padding: 16, border: "1px solid #ccc", minHeight: 100 }}
      />

      {inlineCalc.show && (
        <InlineCalcToast
          result={inlineCalc.result!}
          position={inlineCalc.position}
        />
      )}
    </>
  );
}

API

useInlineCalc(ref) or useInlineCalc(options)

Headless hook for inline math calculation. Two ways to call:

// Simple: pass ref directly
const inlineCalc = useInlineCalc(editorRef);

// With options: pass ref + options, or options with getEditor
const inlineCalc = useInlineCalc(editorRef, { onApply: ... });
const inlineCalc = useInlineCalc({ getEditor: () => editorRef.current });

Options

| Option | Type | Description | |--------|------|-------------| | getEditor | () => HTMLElement \| null | Returns the editor element (not needed if passing ref) | | onApply | (result, expression) => void | Called when result is applied | | onBeforeApply | (context) => void | Intercept apply to handle replacement yourself (see Rich Text Editors) | | 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)

  // Actions
  apply: () => void;          // Apply result (replace expression)
  dismiss: () => void;        // Dismiss (won't re-suggest)
  clear: () => void;          // Clear (will re-suggest)

  // Handlers
  handleKeyDown: (e) => boolean;  // Tab/Escape handler
  handleInput: (text, textNodes?, cursorPosition?) => boolean;  // Detect math
}

InlineCalcToast

Default toast component. Optional — use your own UI with the headless hook.

<InlineCalcToast
  result={150}
  position={{ top: 100, left: 200 }}
  show={true}
  keyLabel="Tab"           // Custom key label
  formatResult={(n) => n.toFixed(2)}  // Custom formatting
  portal={true}            // Render in document.body
  // 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.

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

Supports: +, -, *, x, ×, / with decimals.

Core Functions (Non-React)

For non-React projects or custom integrations, import the core utilities directly:

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");
// => [{ type: "number", value: 100 }, { type: "operator", value: "+" }, ...]

const value = evaluateTokens(tokens);
// => 200

Custom UI Example

function CustomToast({ result, position, onApply }) {
  return (
    <div
      style={{
        position: "fixed",
        top: position.top,
        left: position.left,
        background: "#1a1a1a",
        color: "#fff",
        padding: "8px 12px",
        borderRadius: 8,
      }}
    >
      <span>= {result}</span>
      <button onClick={onApply}>Apply</button>
    </div>
  );
}

// In your component:
{inlineCalc.show && (
  <CustomToast
    result={inlineCalc.result}
    position={inlineCalc.position}
    onApply={inlineCalc.apply}
  />
)}

With Framer Motion

import { motion, AnimatePresence } from "framer-motion";

<AnimatePresence>
  {inlineCalc.show && (
    <InlineCalcToast
      result={inlineCalc.result!}
      position={inlineCalc.position}
      as={motion.div}
      animationProps={{
        initial: { opacity: 0, y: -4, scale: 0.96 },
        animate: { opacity: 1, y: 0, scale: 1 },
        exit: { opacity: 0, y: -4, scale: 0.96 },
        transition: { duration: 0.12 },
      }}
    />
  )}
</AnimatePresence>

Rich Text Editors

For rich text editors like ProseMirror, Slate, or Lexical, use onBeforeApply to handle text replacement with the editor's native API instead of DOM manipulation.

ProseMirror / TipTap

import { useEditor } from "@tiptap/react";
import { useInlineCalc, InlineCalcToast, getTextNodes } from "react-inline-calc";

function TipTapEditor() {
  const editor = useEditor({ extensions: [StarterKit] });

  const inlineCalc = useInlineCalc({
    getEditor: () => document.querySelector(".ProseMirror"),
    onBeforeApply: ({ expression, formattedResult, preventDefault }) => {
      preventDefault(); // Skip default DOM mutation
      // Use TipTap's transaction API
      editor?.commands.insertContent(formattedResult);
    },
  });

  // ... rest of setup
}

Slate

import { useSlate } from "slate-react";
import { Transforms } from "slate";
import { useInlineCalc } from "react-inline-calc";

function SlateEditor() {
  const editor = useSlate();

  const inlineCalc = useInlineCalc({
    getEditor: () => document.querySelector("[data-slate-editor]"),
    onBeforeApply: ({ expression, formattedResult, startIndex, endIndex, preventDefault }) => {
      preventDefault();
      // Use Slate's transform API
      Transforms.delete(editor, {
        at: { anchor: { path: [0, 0], offset: startIndex }, focus: { path: [0, 0], offset: endIndex } },
      });
      Transforms.insertText(editor, formattedResult);
    },
  });

  // ... rest of setup
}

Lexical

import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { $getSelection, $createTextNode } from "lexical";
import { useInlineCalc } from "react-inline-calc";

function LexicalEditor() {
  const [editor] = useLexicalComposerContext();

  const inlineCalc = useInlineCalc({
    getEditor: () => document.querySelector("[contenteditable]"),
    onBeforeApply: ({ formattedResult, preventDefault }) => {
      preventDefault();
      // Use Lexical's update API
      editor.update(() => {
        const selection = $getSelection();
        selection?.insertText(formattedResult);
      });
    },
  });

  // ... rest of setup
}

Cursor Position for Accurate Detection

For editors where cursor position matters, pass it as the third argument to handleInput:

inlineCalc.handleInput(text, textNodes, cursorPosition);

This ensures math expressions are only detected near the cursor, not elsewhere in the document.

Browser Support

Works in all modern browsers. Uses contentEditable and Selection APIs.

License

MIT