Files
soroban-abacus-flashcards/packages/abacus-react
semantic-release-bot 8f8af92286 chore(abacus-react): release v2.16.0 [skip ci]
# [2.16.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.15.0...abacus-react-v2.16.0) (2025-12-19)

### Bug Fixes

* **blog:** correct misleading claim about BKT feeding problem generation ([184cba0](184cba0ec8))
* **blog:** regenerate trajectory data from correct snapshot ([ce85565](ce85565f06))
* **dashboard:** make student dashboard responsive for small screens ([129907f](129907fcc6))
* **dashboard:** use React Query mutations instead of direct fetch ([ff7554b](ff7554b005))
* **migration:** add statement-breakpoint between CREATE and INSERT ([ba68cfc](ba68cfc75d))
* **practice:** add comprehensive logging and validation in recordSlotResult ([85d36c8](85d36c80a2))
* **practice:** add defensive checks in recordSlotResult ([a33e3e6](a33e3e6d2b))
* **practice:** add responsive styles to SessionModeBanner for small screens ([be08efe](be08efe06f))
* **practice:** check all later prefix sums for ambiguity, not just final answer ([43e7db4](43e7db4e88))
* **practice:** correct five complement skill detection for addition and subtraction ([1139c4d](1139c4d1a1))
* **practice:** correct pause phrase attribution ([cc5bb47](cc5bb479c6))
* **practice:** correct route path for resume session ([1a7945d](1a7945dd0b))
* **practice:** disable auto-scroll and add modern PWA meta tag ([8a9afa8](8a9afa86bc))
* **practice:** ensure badges are never taller than wide ([5730bd6](5730bd6112))
* **practice:** ensure keypad spans full screen width ([4b8cbdf](4b8cbdf83c))
* **practice:** ensure speed meter bar is always visible ([0c40dd5](0c40dd5c42))
* **practice:** fix circular import causing REINFORCEMENT_CONFIG.creditMultipliers to be undefined ([147974a](147974a9f0))
* **practice:** fix invisible resume button by using inline styles ([dd3dd45](dd3dd4507c))
* **practice:** handle paused state transitions and add complete phase ([36c9ec3](36c9ec3301))
* **practice:** improve dark mode contrast for sub-nav buttons ([59f574c](59f574c178))
* **practice:** improve mobile layout + floating abacus positioning ([3c9406a](3c9406afc5))
* **practice:** include endEarly.data in currentPlan priority chain ([28b3b30](28b3b30da6))
* **practice:** make session plan page self-sufficient for data loading ([7243502](7243502873))
* **practice:** move SessionPausedModal into ActiveSession for single pause state ([f0a9608](f0a9608a6b))
* **practice:** only show landscape keypad on phone-sized screens ([6c09976](6c09976d4b))
* **practice:** prevent keypad from covering nav and content ([839171c](839171c0ff))
* **practice:** prevent stray "0" rendering in problem area ([7a2390b](7a2390bd1b))
* **practice:** remove empty spacer button from keypad layout ([1058f41](1058f411c6))
* **practice:** remove fallback random problem generation ([f95456d](f95456dadc))
* **practice:** size answer boxes for intermediate prefix sums ([5cfbeeb](5cfbeeb8df))
* **practice:** state-aware complexity selection with graceful fallback ([6c88dcf](6c88dcfdc5))
* **practice:** update pun to "We pressed paws!" ([4800a48](4800a48128))
* **practice:** use inline styles for progress bar ([f45428e](f45428ed82))
* **practice:** use raw CSS media query for landscape keypad visibility ([31fbf80](31fbf80b8f))
* **practice:** use React Query cache for /resume page session data ([ae1a0a8](ae1a0a8e2d))
* **StartPracticeModal:** responsive improvements + integrated tutorial CTA ([56742c5](56742c511d))
* sync pause state between modal and ActiveSession ([55e5c12](55e5c121f1))

### Features

* **abacus:** add dockable abacus feature for practice sessions ([5fb4751](5fb4751728))
* **abacus:** add smooth animated transitions for dock/undock ([2c832c7](2c832c7944))
* **bkt:** add adaptive-bkt mode with unified BKT architecture ([7085a4b](7085a4b3df))
* **bkt:** implement adaptive skill targeting with validated convergence ([354ada5](354ada596d))
* **blog:** add Bayesian blame attribution validation and address reviewer feedback ([ceadd9d](ceadd9de67))
* **blog:** add interactive ECharts for BKT validation blog post ([6a4dd69](6a4dd694a2))
* **blog:** add layered skill trajectory visualization ([b227162](b227162da6))
* **blog:** add session 0, line thickness, and category averages to charts ([c40baee](c40baee43f))
* **blog:** show adaptive vs classic comparison on same chart ([b0c0f5c](b0c0f5c2da))
* **blog:** simplify All Skills chart to show average comparison ([6ef329d](6ef329dd60))
* **practice:** add "Press paws!" pun to auto-pause phrases ([8405f64](8405f64486))
* **practice:** add /resume route for "Welcome back" experience ([7b476e8](7b476e80c1))
* **practice:** add 30 and 45 minute session duration options ([e42766c](e42766c893))
* **practice:** add auto-pause and improve docked abacus sizing ([9c1fd85](9c1fd85ed5))
* **practice:** add browse mode navigation and improve SpeedMeter timing display ([3c52e60](3c52e607b3))
* **practice:** add cascading regrouping skills and improve help UX ([7cf689c](7cf689c3d9))
* **practice:** add celebration progression banner with smooth transitions ([bb9506b](bb9506b93e))
* **practice:** add complexity budget system and toggleable session parts ([5d61de4](5d61de4bf6))
* **practice:** add inline practice panel for browse mode debugging ([c0764cc](c0764ccd85))
* **practice:** add pause info with response time statistics to paused modal ([826c849](826c8490ba))
* **practice:** add play emoji to Keep Going button ([80a33bc](80a33bcae2))
* **practice:** add prefix sum disambiguation and debug panel ([46ff5f5](46ff5f528a))
* **practice:** add projecting SessionModeBanner with slot-based animation ([0f84ede](0f84edec0a))
* **practice:** add Remediation CTA for weak skill focus sessions ([7d8bb2f](7d8bb2f525))
* **practice:** add response time tracking and live timing display ([18ce1f4](18ce1f41af))
* **practice:** add SkillUnlockBanner + session summary improvements ([4daf7b7](4daf7b7433))
* **practice:** add student notes with animated modal + BKT improvements ([2702ec5](2702ec585f))
* **practice:** add subtraction support to problem generator ([4f7a9d7](4f7a9d76cd))
* **practice:** add unified SessionMode system for consistent skill targeting ([b345baf](b345baf3c4))
* **practice:** consolidate nav with transport dropdown and mood indicator ([8851be5](8851be5948))
* **practice:** improve docked abacus UX and submit button behavior ([60fc81b](60fc81bc2d))
* **practice:** improve help mode UX with crossfade and dismiss behaviors ([bcb1c7a](bcb1c7a173))
* **practice:** improve modal UI with problem counts and time estimation ([34d0232](34d0232451))
* **practice:** improve session summary UI ([a27fb0c](a27fb0c9a4))
* **practice:** inline emoji with random pause phrases ([c13fedd](c13feddfbb))
* **practice:** integrate timing display into sub-nav with mobile support ([2fca17a](2fca17a58b))
* **practice:** migrate mastery model to isPracticing + computed fluency ([b2e7268](b2e7268e7a))
* **practice:** redesign paused modal with kid-friendly statistics UX ([11ecb38](11ecb385ad))
* **practice:** reduce term count for visualization part ([9159608](9159608dcd))
* **practice:** refactor disambiguation into state machine with comprehensive tests ([ed277ef](ed277ef745))
* **practice:** responsive mobile keypad and unified skill detection ([ee8dccd](ee8dccd83a))
* **practice:** separate phrase sets for manual vs auto pause ([652519f](652519f219))
* **practice:** unify dashboard with session-aware progress display ([c40543a](c40543ac64))
* **practice:** use student's actual mastered skills for problem generation ([245cc26](245cc269fe))
* **session-planner:** integrate SessionMode for single source of truth targeting ([9851c01](9851c01026))
* **skills-modal:** add spring animations and UX improvements ([b94f533](b94f5338e5))
* **skills:** add Skills Dashboard with honest skill assessment framing ([bf4334b](bf4334b281))
* **test:** add journey simulator for BKT A/B testing ([86cd518](86cd518c39))
* **tutorial:** implement subtraction in unified step generator ([e5c697b](e5c697b7a8))
2025-12-19 01:51:28 +00:00
..

@soroban/abacus-react

A comprehensive React component for rendering interactive Soroban (Japanese abacus) visualizations with advanced customization and tutorial capabilities.

Features

  • 🎯 Interactive beads - Click to toggle or use directional gestures
  • 🎨 Complete visual customization - Style every element individually
  • 📱 Responsive scaling - Configurable scale factor for different sizes
  • 🌈 Multiple color schemes - Monochrome, place-value, alternating, heaven-earth
  • 🎭 Flexible shapes - Diamond, square, or circle beads
  • React Spring animations - Smooth bead movements and transitions
  • 🔧 Developer-friendly - Comprehensive hooks and callback system
  • 🎓 Tutorial system - Built-in overlay and guidance capabilities
  • 🧩 Framework-free SVG - Complete control over rendering
  • 3D Enhancement - Three levels of progressive 3D effects for immersive visuals
  • 🚀 Server Component support - AbacusStatic works in React Server Components (Next.js App Router)

Installation

npm install @soroban/abacus-react
# or
pnpm add @soroban/abacus-react
# or
yarn add @soroban/abacus-react

Quick Start

Basic Usage

Simple abacus showing a number

Basic Usage
<AbacusReact value={123} columns={3} showNumbers={true} scaleFactor={1.0} />

Interactive Mode

Clickable abacus with animations

Interactive Mode
<AbacusReact
  value={456}
  columns={3}
  interactive={true}
  animated={true}
  showNumbers={true}
  callbacks={{
    onValueChange: (newValue) => console.log("New value:", newValue),
    onBeadClick: (event) => console.log("Bead clicked:", event),
  }}
/>

Custom Styling

Personalized colors and highlights

Custom Styling
<AbacusReact
  value={789}
  columns={3}
  colorScheme="place-value"
  beadShape="circle"
  customStyles={{
    heavenBeads: { fill: "#ff6b35" },
    earthBeads: { fill: "#3498db" },
    numerals: { color: "#2c3e50", fontWeight: "bold" },
  }}
  highlightBeads={[{ columnIndex: 1, beadType: "heaven" }]}
/>

Theme Presets

Use pre-defined themes for quick styling:

import { AbacusReact, ABACUS_THEMES } from '@soroban/abacus-react';

// Available themes: 'light', 'dark', 'trophy', 'translucent', 'solid', 'traditional'
<AbacusReact
  value={123}
  columns={3}
  customStyles={ABACUS_THEMES.dark}
/>

<AbacusReact
  value={456}
  columns={3}
  customStyles={ABACUS_THEMES.trophy}  // Golden frame for achievements
/>

<AbacusReact
  value={789}
  columns={3}
  customStyles={ABACUS_THEMES.traditional}  // Brown wooden appearance
/>

Available Themes:

  • light - Solid white frame with subtle gray accents (best for light backgrounds)
  • dark - Translucent white with subtle glow (best for dark backgrounds)
  • trophy - Golden frame with warm tones (best for achievements/rewards)
  • translucent - Nearly invisible frame (best for inline/minimal UI)
  • solid - Black frame (best for high contrast/educational contexts)
  • traditional - Brown wooden appearance (best for traditional soroban aesthetic)

Static Display (Server Components)

For static, non-interactive displays that work in React Server Components:

// IMPORTANT: Use /static import path for RSC compatibility!
import { AbacusStatic } from "@soroban/abacus-react/static";

// ✅ Works in React Server Components - no "use client" needed!
// ✅ No JavaScript sent to client
// ✅ Perfect for SSG, SSR, and static previews

<AbacusStatic value={123} columns="auto" hideInactiveBeads compact />;

Import paths:

  • @soroban/abacus-react - Full package (client components with hooks/animations)
  • @soroban/abacus-react/static - Server-compatible components only (no client code)

Guaranteed Visual Consistency:

Both AbacusStatic and AbacusReact share the same underlying layout engine. Same props = same exact SVG output. This ensures:

  • Static previews match interactive versions pixel-perfect
  • Server-rendered abaci look identical to client-rendered ones
  • PDF generation produces accurate representations
  • No visual discrepancies between environments

Architecture: How We Guarantee Consistency

The package uses a shared rendering architecture with dependency injection:

┌─────────────────────────────────────────────┐
│ Shared Utilities (AbacusUtils.ts)          │
│ • calculateStandardDimensions() - Single    │
│   source of truth for all layout dimensions│
│ • calculateBeadPosition() - Exact bead      │
│   positioning using shared formulas        │
└────────────┬────────────────────────────────┘
             │
             ├──────────────────────────────────┐
             ↓                                  ↓
   ┌─────────────────┐              ┌─────────────────┐
   │ AbacusStatic    │              │ AbacusReact     │
   │ (Server/Static) │              │ (Interactive)   │
   └────────┬────────┘              └────────┬────────┘
            │                                │
            └────────────┬───────────────────┘
                         ↓
             ┌────────────────────────┐
             │ AbacusSVGRenderer      │
             │ • Pure SVG structure   │
             │ • Dependency injection │
             │ • Bead component prop  │
             └────────────────────────┘
                         ↓
         ┌───────────────┴───────────────┐
         ↓                               ↓
   ┌──────────────┐              ┌──────────────────┐
   │ AbacusStatic │              │ AbacusAnimated   │
   │ Bead         │              │ Bead             │
   │ (Simple SVG) │              │ (react-spring)   │
   └──────────────┘              └──────────────────┘

Key Components:

  1. calculateStandardDimensions() - Returns complete layout dimensions (bar position, bead sizes, gaps, etc.)
  2. calculateBeadPosition() - Calculates exact x,y coordinates for any bead
  3. AbacusSVGRenderer - Shared SVG rendering component that accepts a bead component via dependency injection
  4. AbacusStaticBead - Simple SVG shapes for static display (no hooks, RSC-compatible)
  5. AbacusAnimatedBead - Client component with react-spring animations and gesture handling

This architecture eliminates code duplication (~560 lines removed in the refactor) while guaranteeing pixel-perfect consistency.

When to use AbacusStatic vs AbacusReact:

Feature AbacusStatic AbacusReact
React Server Components Yes No (requires "use client")
Client-side JavaScript None Yes
User interaction No Click/drag beads
Animations No Smooth transitions
Sound effects No Optional sounds
3D effects No Yes
Visual output Identical Identical
Bundle size 📦 Minimal 📦 Full-featured
Use cases Preview cards, thumbnails, static pages, PDFs Interactive tutorials, games, tools
// Example: Server Component with static abacus cards
// app/flashcards/page.tsx
import { AbacusStatic } from "@soroban/abacus-react/static";

export default function FlashcardsPage() {
  const numbers = [1, 5, 10, 25, 50, 100];

  return (
    <div className="grid grid-cols-3 gap-4">
      {numbers.map((num) => (
        <div key={num} className="card">
          <AbacusStatic value={num} columns="auto" compact />
          <p>{num}</p>
        </div>
      ))}
    </div>
  );
}

Compact/Inline Display

Create mini abacus displays for inline use:

// Compact mode - automatically hides frame and optimizes spacing
<AbacusReact
  value={7}
  columns={1}
  compact={true}
  hideInactiveBeads={true}
  scaleFactor={0.7}
/>

// Or manually control frame visibility
<AbacusReact
  value={42}
  columns={2}
  frameVisible={false}  // Hide column posts and reckoning bar
/>

Tutorial System

Educational guidance with tooltips and column highlighting

Tutorial System
<AbacusReact
  value={42}
  columns={3}
  interactive={true}
  // Highlight the tens column with a label
  highlightColumns={[1]} // Highlight column index 1 (tens)
  columnLabels={["ones", "tens", "hundreds"]} // Add labels to columns
  overlays={[
    {
      id: "tip",
      type: "tooltip",
      target: {
        type: "bead",
        columnIndex: 1,
        beadType: "earth",
        beadPosition: 1,
      },
      content: <div>Click this bead in the tens column!</div>,
      offset: { x: 0, y: -30 },
    },
  ]}
  callbacks={{
    onBeadClick: (event) => {
      if (
        event.columnIndex === 1 &&
        event.beadType === "earth" &&
        event.position === 1
      ) {
        console.log("Correct! You clicked the tens column.");
      }
    },
  }}
/>

Column Highlighting:

  • highlightColumns - Array of column indices to highlight (e.g., [0, 2] highlights first and third columns)
  • columnLabels - Optional labels displayed above each column (indexed left to right)

3D Enhancement

Make the abacus feel tangible and satisfying with three progressive levels of 3D effects.

Subtle Mode

Light depth shadows and perspective for subtle dimensionality.

<AbacusReact
  value={12345}
  columns={5}
  enhanced3d="subtle"
  interactive
  animated
/>

Realistic Mode

Material-based rendering with lighting effects and textures.

<AbacusReact
  value={7890}
  columns={4}
  enhanced3d="realistic"
  material3d={{
    heavenBeads: "glossy", // 'glossy' | 'satin' | 'matte'
    earthBeads: "satin",
    lighting: "top-down", // 'top-down' | 'ambient' | 'dramatic'
    woodGrain: true, // Add wood texture to frame
  }}
  interactive
  animated
/>

Materials:

  • glossy - High shine with strong highlights
  • satin - Balanced shine (default)
  • matte - Subtle shading, no shine

Lighting:

  • top-down - Balanced directional light from above
  • ambient - Soft light from all directions
  • dramatic - Strong directional light for high contrast

Delightful Mode

Maximum satisfaction with enhanced physics and interactive effects.

<AbacusReact
  value={8642}
  columns={4}
  enhanced3d="delightful"
  material3d={{
    heavenBeads: "glossy",
    earthBeads: "satin",
    lighting: "dramatic",
    woodGrain: true,
  }}
  physics3d={{
    hoverParallax: true, // Beads lift on hover with Z-depth
  }}
  interactive
  animated
  soundEnabled
/>

Physics Options:

  • hoverParallax - Beads near mouse cursor lift up with depth perception

All 3D modes work with existing configurations and preserve exact geometry.

Core API

Basic Props

interface AbacusConfig {
  // Display
  value?: number; // 0-99999, number to display
  columns?: number | "auto"; // Number of columns or auto-calculate
  showNumbers?: boolean; // Show place value numbers
  scaleFactor?: number; // 0.5 - 3.0, size multiplier

  // Appearance
  beadShape?: "diamond" | "square" | "circle";
  colorScheme?: "monochrome" | "place-value" | "alternating" | "heaven-earth";
  colorPalette?: "default" | "colorblind" | "mnemonic" | "grayscale" | "nature";
  hideInactiveBeads?: boolean; // Hide/show inactive beads

  // Layout & Frame
  frameVisible?: boolean; // Show/hide column posts and reckoning bar
  compact?: boolean; // Compact layout (implies frameVisible=false)

  // Interaction
  interactive?: boolean; // Enable user interactions
  animated?: boolean; // Enable animations
  gestures?: boolean; // Enable drag gestures

  // Tutorial Features
  highlightColumns?: number[]; // Highlight specific columns by index
  columnLabels?: string[]; // Optional labels for columns
}

Event Callbacks

interface AbacusCallbacks {
  onValueChange?: (newValue: number) => void;
  onBeadClick?: (event: BeadClickEvent) => void;
  onBeadHover?: (event: BeadClickEvent) => void;
  onBeadLeave?: (event: BeadClickEvent) => void;
  onColumnClick?: (columnIndex: number) => void;
  onNumeralClick?: (columnIndex: number, value: number) => void;
  onBeadRef?: (bead: BeadConfig, element: SVGElement | null) => void;
}

interface BeadClickEvent {
  columnIndex: number; // 0, 1, 2...
  beadType: "heaven" | "earth"; // Type of bead
  position: number; // Position within type (0-3 for earth)
  active: boolean; // Current state
  value: number; // Numeric value (1 or 5)
  bead: BeadConfig; // Full bead configuration
}

Advanced Customization

Granular Styling

Target any visual element with precise control:

const customStyles = {
  // Global defaults
  heavenBeads: { fill: "#ff6b35" },
  earthBeads: { fill: "#3498db" },
  activeBeads: { opacity: 1.0 },
  inactiveBeads: { opacity: 0.3 },

  // Column-specific overrides
  columns: {
    0: {
      // Hundreds column
      heavenBeads: { fill: "#e74c3c" },
      earthBeads: { fill: "#2ecc71" },
    },
  },

  // Individual bead targeting
  beads: {
    1: {
      // Middle column
      heaven: { fill: "#f39c12" },
      earth: {
        0: { fill: "#1abc9c" }, // First earth bead
        3: { fill: "#e67e22" }, // Fourth earth bead
      },
    },
  },

  // UI elements
  reckoningBar: { stroke: "#34495e", strokeWidth: 3 },
  columnPosts: { stroke: "#7f8c8d" },
  numerals: {
    color: "#2c3e50",
    fontSize: "14px",
    fontFamily: "monospace",
  },
};

<AbacusReact customStyles={customStyles} />;

Tutorial and Overlay System

Create interactive educational experiences:

const overlays = [
  {
    id: "welcome-tooltip",
    type: "tooltip",
    target: {
      type: "bead",
      columnIndex: 0,
      beadType: "earth",
      beadPosition: 0,
    },
    content: (
      <div
        style={{
          background: "#333",
          color: "white",
          padding: "8px",
          borderRadius: "4px",
        }}
      >
        Click me to start!
      </div>
    ),
    offset: { x: 0, y: -30 },
  },
];

<AbacusReact
  overlays={overlays}
  highlightBeads={[{ columnIndex: 0, beadType: "earth", position: 0 }]}
  callbacks={{
    onBeadClick: (event) => {
      if (
        event.columnIndex === 0 &&
        event.beadType === "earth" &&
        event.position === 0
      ) {
        console.log("Tutorial step completed!");
      }
    },
  }}
/>;

Bead Reference System

Access individual bead DOM elements for advanced positioning:

function AdvancedExample() {
  const beadRefs = useRef(new Map<string, SVGElement>());

  const handleBeadRef = (bead: BeadConfig, element: SVGElement | null) => {
    const key = `${bead.columnIndex}-${bead.type}-${bead.position}`;
    if (element) {
      beadRefs.current.set(key, element);

      // Now you can position tooltips, highlights, etc. precisely
      const rect = element.getBoundingClientRect();
      console.log(`Bead at column ${bead.columnIndex} is at:`, rect);
    }
  };

  return (
    <AbacusReact
      callbacks={{ onBeadRef: handleBeadRef }}
      // ... other props
    />
  );
}

Hooks

useAbacusDiff

Calculate bead differences between values for tutorials and animations:

import { useAbacusDiff } from "@soroban/abacus-react";

function Tutorial() {
  const [currentValue, setCurrentValue] = useState(5);
  const targetValue = 15;

  // Get diff information: which beads need to move
  const diff = useAbacusDiff(currentValue, targetValue);

  return (
    <div>
      <p>{diff.summary}</p> {/* "add heaven bead in tens column, then..." */}
      <AbacusReact
        value={currentValue}
        stepBeadHighlights={diff.highlights} // Highlight beads that need to change
        interactive
        onValueChange={setCurrentValue}
      />
      <p>Changes needed: {diff.changes.length}</p>
    </div>
  );
}

Returns:

  • changes - Array of bead movements with direction and order
  • highlights - Bead highlight data for stepBeadHighlights prop
  • hasChanges - Boolean indicating if any changes needed
  • summary - Human-readable description of changes (e.g., "add heaven bead in ones column")

useAbacusState

Convert numbers to abacus bead states:

import { useAbacusState } from "@soroban/abacus-react";

function BeadAnalyzer() {
  const value = 123;
  const state = useAbacusState(value);

  // Check bead positions
  const onesHasHeaven = state[0].heavenActive; // false (3 < 5)
  const tensEarthCount = state[1].earthActive; // 2 (20 = 2 tens)

  return (
    <div>Ones column heaven bead: {onesHasHeaven ? "active" : "inactive"}</div>
  );
}

useAbacusDimensions

Get exact sizing information for layout planning:

import { useAbacusDimensions } from "@soroban/abacus-react";

function MyComponent() {
  const dimensions = useAbacusDimensions(3, 1.2); // 3 columns, 1.2x scale

  return (
    <div style={{ width: dimensions.width, height: dimensions.height }}>
      <AbacusReact columns={3} scaleFactor={1.2} />
    </div>
  );
}

Utility Functions

Low-level functions for working with abacus states and calculations:

numberToAbacusState

Convert a number to bead positions:

import { numberToAbacusState } from "@soroban/abacus-react";

const state = numberToAbacusState(123, 5); // 5 columns
// Returns: {
//   0: { heavenActive: false, earthActive: 3 },  // ones = 3
//   1: { heavenActive: false, earthActive: 2 },  // tens = 2
//   2: { heavenActive: true, earthActive: 0 },   // hundreds = 1
//   ...
// }

abacusStateToNumber

Convert bead positions back to a number:

import { abacusStateToNumber } from "@soroban/abacus-react";

const state = {
  0: { heavenActive: false, earthActive: 3 },
  1: { heavenActive: false, earthActive: 2 },
  2: { heavenActive: true, earthActive: 0 },
};

const value = abacusStateToNumber(state); // 123

calculateBeadDiff

Calculate the exact bead movements needed between two states:

import { calculateBeadDiff, numberToAbacusState } from "@soroban/abacus-react";

const fromState = numberToAbacusState(5);
const toState = numberToAbacusState(15);
const diff = calculateBeadDiff(fromState, toState);

console.log(diff.summary); // "add heaven bead in tens column"
console.log(diff.changes); // Detailed array of movements with order

calculateBeadDiffFromValues

Convenience wrapper for calculating diff from numbers:

import { calculateBeadDiffFromValues } from "@soroban/abacus-react";

const diff = calculateBeadDiffFromValues(42, 57);
// Equivalent to: calculateBeadDiff(numberToAbacusState(42), numberToAbacusState(57))

validateAbacusValue

Check if a value is within the supported range:

import { validateAbacusValue } from "@soroban/abacus-react";

const result = validateAbacusValue(123456, 5); // 5 columns max
console.log(result.isValid); // false
console.log(result.error); // "Value exceeds maximum for 5 columns (max: 99999)"

areStatesEqual

Compare two abacus states:

import { areStatesEqual, numberToAbacusState } from "@soroban/abacus-react";

const state1 = numberToAbacusState(123);
const state2 = numberToAbacusState(123);
const isEqual = areStatesEqual(state1, state2); // true

calculateStandardDimensions

Core Architecture Function - Calculate complete layout dimensions for consistent rendering.

This is the single source of truth for all layout dimensions, used internally by both AbacusStatic and AbacusReact to guarantee pixel-perfect consistency.

import { calculateStandardDimensions } from "@soroban/abacus-react";

const dimensions = calculateStandardDimensions({
  columns: 3,
  scaleFactor: 1.5,
  showNumbers: true,
  columnLabels: ["ones", "tens", "hundreds"],
});

// Returns complete layout info:
// {
//   width, height,           // SVG canvas size
//   beadSize,                // 12 * scaleFactor (standard bead size)
//   rodSpacing,              // 25 * scaleFactor (column spacing)
//   rodWidth,                // 3 * scaleFactor
//   barThickness,            // 2 * scaleFactor
//   barY,                    // Reckoning bar Y position (30 * scaleFactor + labels)
//   heavenY, earthY,         // Inactive bead rest positions
//   activeGap,               // 1 * scaleFactor (gap to bar when active)
//   inactiveGap,             // 8 * scaleFactor (gap between active/inactive)
//   adjacentSpacing,         // 0.5 * scaleFactor (spacing between adjacent beads)
//   padding, labelHeight, numbersHeight, totalColumns
// }

Why this matters: Same input parameters = same exact layout dimensions = pixel-perfect visual consistency across static and interactive displays.

calculateBeadPosition

Core Architecture Function - Calculate exact x,y coordinates for any bead.

Used internally by AbacusSVGRenderer to position all beads consistently in both static and interactive modes.

import {
  calculateBeadPosition,
  calculateStandardDimensions,
} from "@soroban/abacus-react";

const dimensions = calculateStandardDimensions({ columns: 3, scaleFactor: 1 });
const bead = {
  type: "heaven",
  active: true,
  position: 0,
  placeValue: 1, // tens column
};

const position = calculateBeadPosition(bead, dimensions);
// Returns: { x: 25, y: 29 }  // exact pixel coordinates

Useful for custom rendering or positioning tooltips/overlays relative to specific beads.

Educational Use Cases

Interactive Math Lessons

function MathLesson() {
  const [problem, setProblem] = useState({ a: 23, b: 45 });
  const [step, setStep] = useState("show-first");

  return (
    <div>
      <h3>
        Add {problem.a} + {problem.b}
      </h3>

      <AbacusReact
        value={step === "show-first" ? problem.a : 0}
        interactive={step === "add-second"}
        callbacks={{
          onValueChange: (value) => {
            if (value === problem.a + problem.b) {
              celebrate();
            }
          },
        }}
      />
    </div>
  );
}

Assessment Tools

function AbacusQuiz() {
  const [answers, setAnswers] = useState([]);

  const checkAnswer = (event: BeadClickEvent) => {
    const isCorrect = validateBeadClick(event, expectedAnswer);
    recordAnswer(event, isCorrect);

    if (isCorrect) {
      showSuccessFeedback();
    } else {
      showHint(event);
    }
  };

  return (
    <AbacusReact
      interactive={true}
      callbacks={{ onBeadClick: checkAnswer }}
      customStyles={getAnswerHighlighting(answers)}
    />
  );
}

TypeScript Support

Full TypeScript definitions included:

import {
  // Components
  AbacusReact,

  // Hooks
  useAbacusDiff,
  useAbacusState,
  useAbacusDimensions,

  // Utility Functions
  numberToAbacusState,
  abacusStateToNumber,
  calculateBeadDiff,
  calculateBeadDiffFromValues,
  validateAbacusValue,
  areStatesEqual,
  calculateStandardDimensions, // NEW: Shared layout calculator
  calculateBeadPosition, // NEW: Bead position calculator

  // Theme Presets
  ABACUS_THEMES,

  // Types
  AbacusConfig,
  BeadConfig,
  BeadClickEvent,
  AbacusCustomStyles,
  AbacusOverlay,
  AbacusCallbacks,
  AbacusState,
  BeadState,
  BeadDiffResult,
  BeadDiffOutput,
  AbacusThemeName,
  AbacusLayoutDimensions, // NEW: Complete layout dimensions type
  BeadPositionConfig, // NEW: Bead config for position calculation
} from "@soroban/abacus-react";

// All interfaces fully typed for excellent developer experience

Contributing

Contributions welcome! Please see our contributing guidelines and feel free to submit issues or pull requests.

License

MIT License - see LICENSE file for details.