Skip to main content

Spreadsheet Engine (Gridheim)

Get up and running with Gridheim, our lightweight spreadsheet component.

What It Looks Like

┌─────────────────────────────────────┐
│ A1 │ 100 │ ← Formula bar
├─────────────────────────────────────┤
│ 100 │ 200 │ 300 │
│ 400 │ 500 │ 600 │
│ 700 │ 800 │ 900 │
│ │ │ │
│ │ │ │
└─────────────────────────────────────┘

No headers. No tabs. Just cells.

Basic Setup

import Gridheim from "@/components/Gridheim";

function App() {
return (
<Gridheim
workbookId="wb-123"
sheetId="sheet-1"
initialCells={[
{ address: "A1", value: "10" },
{ address: "B1", value: "20" },
{ address: "C1", value: "30" },
]}
/>
);
}

That's it.

Keyboard Shortcuts

ShortcutAction
Arrow keysMove between cells
Ctrl+CCopy
Ctrl+XCut
Ctrl+VPaste
DeleteClear cell
F2Edit cell
Double-clickEdit cell
EnterConfirm edit, move down
EscCancel edit
TabMove right
Shift+TabMove left

File Structure

web/src/components/Gridheim/
├── redux/
│ └── gridSlice.ts ← Redux state
├── hooks/
│ └── useGridState.ts ← State hook
├── components/
│ ├── Gridheim.tsx ← Main component
│ ├── VirtualGrid.tsx ← Grid renderer
│ └── FormulaBar.tsx ← Formula input
├── styles/
│ ├── Gridheim.module.css
│ ├── VirtualGrid.module.css
│ └── FormulaBar.module.css
└── utils/
├── addressUtils.ts ← A1 notation (240 lines)
└── formulaUtils.ts ← Formula parsing (370 lines)

Props Reference

interface GridheimProps {
workbookId: string; // Required: workbook ID
sheetId: string; // Required: sheet ID
initialCells?: Array<{
// Optional: initial data
address: string; // A1, B2, etc.
value: string; // Display value
formula?: string; // =A1+B2
}>;
rows?: number; // Optional: default 100
cols?: number; // Optional: default 26
onCellChange?: (address: string, value: string) => void;
}

Events

onCellChange: (address: string, value: string) => void
// Called when user edits a cell
// Use to persist changes to API

Integration with ThorAPI

import { useGetCellsQuery, useUpdateCellMutation } from "@thorapi/redux/services";
import Gridheim from "@/components/Gridheim";

export function GridheimPage() {
const workbookId = "wb-123";
const sheetId = "sheet-1";

// Load cells
const { data: cells, isLoading } = useGetCellsQuery({ workbookId, sheetId });

// Save cells
const [updateCell] = useUpdateCellMutation();

if (isLoading) return <div>Loading...</div>;

return (
<Gridheim
workbookId={workbookId}
sheetId={sheetId}
initialCells={cells || []}
onCellChange={(address, value) => {
updateCell({
address,
value,
sheetId,
});
}}
/>
);
}

Features

✅ Implemented

  • Display cells in grid
  • Select single cell or range
  • Edit cell (double-click, F2, type)
  • Formula bar
  • Keyboard navigation
  • Copy/paste and cut/paste
  • Delete cell
  • Virtual scrolling
  • Formula support (parsing)
  • In-place editor
  • Tab and arrow key navigation

❌ Not Included (By Design)

  • Column headers (A, B, C)
  • Row numbers (1, 2, 3)
  • Sheet tabs
  • Resize rows/columns
  • Formatting (colors, fonts)
  • Charts or images
  • Data validation
  • Formula evaluation
  • Undo/redo
  • Cell comments

Performance

  • Virtual scrolling: 10,000+ cells without lag
  • Cell size: 100px × 25px (fixed)
  • Memory: ~5MB for 10K cells
  • Render: <100ms initial, 60 FPS scroll
  • Bundle size: ~45KB minified

Styling

Customize by editing CSS modules:

/* VirtualGrid.module.css */
.cell {
width: 100px; /* Change cell width */
height: 25px; /* Change cell height */
border: 1px solid #e0e0e0;
background-color: #fff;
}

.cell.selected {
background-color: #e3f2fd;
border: 2px solid #2196f3;
}

Troubleshooting

Component not rendering?

  • Check that workbookId and sheetId props are provided
  • Ensure React 18+ is installed
  • Verify Redux provider wraps component (if using hooks)

Cells not showing?

  • Confirm initialCells prop is provided
  • Verify address format is correct (A1, B2, etc.)
  • Check that value field is not empty

Keyboard shortcuts not working?

  • Ensure component has focus
  • Check that you're not in edit mode
  • Verify browser isn't capturing keys

Paste not working?

  • Confirm something was copied (Ctrl+C)
  • Verify target cell is selected
  • Check clipboard API is enabled

Demo

function GridheimDemo() {
const [cells, setCells] = React.useState([
{ address: "A1", value: "Item" },
{ address: "B1", value: "Quantity" },
{ address: "C1", value: "Price" },
{ address: "A2", value: "Widget" },
{ address: "B2", value: "10" },
{ address: "C2", value: "99.99" },
{ address: "A3", value: "Gadget" },
{ address: "B3", value: "5" },
{ address: "C3", value: "49.99" },
]);

return (
<Gridheim
workbookId="demo-wb"
sheetId="demo-sheet"
initialCells={cells}
onCellChange={(addr, value) => {
const newCells = cells.map((c) =>
c.address === addr ? { ...c, value } : c
);
setCells(newCells);
}}
/>
);
}

Summary

<Gridheim workbookId="..." sheetId="..." initialCells={[]} />

Questions? Check the source code—it's well documented with JSDoc comments.

READY TO USE