@worksheet-js/core
v1.4.0
Published
The high-performance, virtualized spreadsheet UI engine for modern web applications.
Readme
@worksheet-js/core
Industrial-grade, high-performance spreadsheet engine for the modern web.
@worksheet-js/core is the foundational engine behind the Worksheet.js ecosystem. It delivers a virtualized canvas renderer, a robust sheet state model, background formula recalculation via Web Workers, and a comprehensive public API for building professional-grade spreadsheet applications.
Table of Contents
- Key Features
- Installation
- License Initialization
- Quick Start
- Configuration Options
- Public API
- Event System
- Key Types
- Architecture Notes
- License
Key Features
- Canvas Tile Renderer — Virtualized dirty-tile rendering; only changed tiles repaint on scroll.
- 1M+ Cells at 60 FPS — No DOM cell pool; pure canvas operations with hardware acceleration.
- Progressive Loading — First ~300 rows rendered immediately; remaining rows hydrate in the background.
- Background Formula Engine — Calculations run in a dedicated Web Worker via
CalcWorkerHost. - 300+ Excel Functions — Powered by
@worksheet-js/formulawith full cross-sheet dependency tracking. - High-Fidelity XLSX I/O — Round-trip import/export preserving styles, merges, conditional formatting, and hyperlinks.
- Plugin System — Charts (Chart.js 4.x), Pivot Tables, Data Validation, Conditional Formatting, Comments, Images.
- Freeze Panes — Per-axis frozen row/column rendering with correct hit-test support.
- Find & Replace — Full-sheet search with match highlighting.
- Performance Monitoring — Built-in rolling-window P95/mean/max telemetry across 6 metrics.
- LRU Display Cache — Bounded at 100K entries with proactive eviction on scroll.
- Fully Type-Safe — Strict TypeScript with exhaustive type definitions for all public surfaces.
Installation
npm install @worksheet-js/core chart.js
# or
pnpm add @worksheet-js/core chart.js
# or
yarn add @worksheet-js/core chart.jsRequired peer dependency:
chart.js >= 4.0.0must be installed for the integrated charting plugin. Install it even if you are not using charts.
Framework wrappers (recommended for React/Vue projects):
# React
npm install @worksheet-js/core @worksheet-js/react chart.js
# Vue 3
npm install @worksheet-js/core @worksheet-js/vue chart.jsLicense Initialization
@worksheet-js/core is proprietary software. A license key must be initialized once at application startup, before calling Worksheet.create() or any other API.
import { initializeLicense } from '@worksheet-js/core';
// Call this once at your application's entry point
initializeLicense('YOUR-LICENSE-KEY');Alternatively, pass the key inline via WorksheetOptions:
const sheet = await Worksheet.create({
container: document.getElementById('app')!,
licenseKey: 'YOUR-LICENSE-KEY',
worksheets: [{ worksheetName: 'Sheet1' }],
});To obtain a license key, contact [email protected].
Quick Start
import { Worksheet, initializeLicense } from '@worksheet-js/core';
// 1. Initialize your license
initializeLicense('YOUR-LICENSE-KEY');
// 2. Create the spreadsheet instance
const sheet = await Worksheet.create({
container: document.getElementById('app')!,
worksheets: [
{
worksheetName: 'Annual Report',
minDimensions: [26, 1000], // 26 columns (A–Z), 1000 rows
},
],
toolbar: true,
formulaBar: true,
theme: 'light',
});
// 3. Write values and formulas
sheet.setValue('A1', 'Product');
sheet.setValue('B1', 'Revenue');
sheet.setValue('B2', '15000');
sheet.setValue('B3', '=SUM(B2:B100)');
// 4. Apply styles
sheet.setStyle('A1', { bold: true, color: '#1a1a2e', backgroundColor: '#e8f4f8' });
// 5. Listen for changes
sheet.on('onchange', (ws, cell, x, y, value) => {
console.log(`Cell [${x},${y}] changed to: ${value}`);
});Configuration Options
All options are passed as WorksheetOptions to Worksheet.create().
| Option | Type | Default | Description |
| :----------------- | :----------------------------- | :-------: | :--------------------------------------------------------------------- |
| container | HTMLElement | — | Required. DOM element to mount the spreadsheet into. |
| worksheets | Partial<SheetMeta>[] | [{}] | Initial sheet tab configuration (name, dimensions, frozen panes). |
| toolbar | boolean | true | Show the ribbon toolbar. |
| formulaBar | boolean | true | Show the formula input bar above the grid. |
| theme | 'light' \| 'dark' | 'light' | Color theme. |
| defaultColWidth | number | 100 | Default column width in pixels. |
| defaultRowHeight | number | 24 | Default row height in pixels. |
| frozenRows | number | 0 | Rows to freeze at the top (applies globally across all sheets). |
| frozenCols | number | 0 | Columns to freeze at the left (applies globally across all sheets). |
| licenseKey | string | — | License key (alternative to calling initializeLicense() separately). |
| onSave | (data: any) => Promise<void> | — | Callback when the user triggers Save (Ctrl+S or toolbar save button). |
SheetMeta — per-sheet configuration
| Property | Type | Description |
| :-------------- | :----------------------------- | :-------------------------------------- |
| worksheetName | string | Tab display name. |
| minDimensions | [cols: number, rows: number] | Minimum grid size to render. |
| frozenRows | number | Rows frozen for this specific sheet. |
| frozenCols | number | Columns frozen for this specific sheet. |
| hidden | boolean | Hide the sheet tab from the tab bar. |
Public API
Static Methods
// Create a new spreadsheet instance and mount it into the DOM
Worksheet.create(options: WorksheetOptions): Promise<Worksheet>
// Load an XLSX file from a binary buffer
Worksheet.loadXlsx(buffer: Uint8Array, options: WorksheetOptions): Promise<Worksheet>
// Load from CSV text or binary
Worksheet.loadCsv(data: string | Uint8Array, options: WorksheetOptions): Promise<Worksheet>
// Load from JSON data
Worksheet.loadJson(data: any, options: WorksheetOptions): Promise<Worksheet>Data Methods
// Read a cell value by address (e.g. 'A1') or by zero-based coordinates
getValue(address: string): string
getValue(x: number, y: number): string
// Write a value or formula to a cell
setValue(address: string, value: string): void
setValue(x: number, y: number, value: string): void
// Read the computed style of a cell
getStyle(address: string): Partial<CellStyle>
// Apply style properties to a cell (partial — only specified keys are changed)
setStyle(address: string, style: Partial<CellStyle>): void
setStyle(x: number, y: number, style: Partial<CellStyle>): voidWorksheet Management
// The currently active sheet model (read-only)
get activeWorksheet(): WorksheetModel | null
// Switch the active tab by zero-based index
setActiveWorksheet(index: number): void
// Add a new sheet tab
addWorksheet(options?: Partial<SheetMeta>): Promise<WorksheetModel | null>
// Permanently remove a sheet tab
deleteWorksheet(index: number): void
// Duplicate an existing sheet
duplicateWorksheet(index: number): Promise<void>
// Move a sheet tab from one position to another
moveWorksheet(fromIdx: number, toIdx: number): void
// Rename a sheet tab
renameWorksheet(index: number, name: string): void
// Hide / unhide a sheet tab
hideWorksheet(index: number): void
unhideWorksheet(index: number): voidNavigation & View
// Scroll to a specific row (y) and optionally column (x) — zero-based
goto(y: number, x?: number): void
// Directional navigation (moves selection cursor)
left(): void
right(): void
top(): void
down(): void
first(): void // Jump to A1
last(): void // Jump to the last non-empty cell
// Resize the viewport container
setViewport(width: number | string, height: number | string): void
// Enter or exit fullscreen mode
fullscreen(state: boolean): void
// Read the current zoom level (percentage, e.g. 100)
getZoom(): number
// Set the zoom level (e.g. 75, 100, 125, 150)
setZoom(value: number): void
// Switch color theme
setTheme(theme: 'light' | 'dark'): void
// Override theme color tokens
setColors(colors: Partial<ThemeColors>): void
// Override theme font stack
setFonts(fonts: ThemeFonts): void
// Change the default font family
setDefaultFont(displayName: string, cssFamily?: string): voidFormulas & Computation
// Enable or disable background formula recalculation
calculations(state: boolean): void
// Evaluate a formula expression ad-hoc (not stored in any cell)
executeFormula(expression: string, x?: number, y?: number, caching?: boolean): unknown
// Read from the volatile per-cell cache
getCache(cellName: string): unknown
// Write to the volatile per-cell cache (single entry or batch)
setCache(cellName: string, value: unknown): void
setCache(entries: Record<string, unknown>): voidImport & Export
// Import an XLSX or CSV file from a browser File object (format auto-detected)
importFromFile(file: File): Promise<void>
// Export the workbook as a downloadable XLSX file
exportXlsx(filename?: string): Promise<void>
// Export the active sheet as a downloadable CSV file
exportCsv(filename?: string): Promise<void>
// Trigger the configured onSave callback (or fall back to a download)
save(): Promise<void>
// Mark the workbook as having unsaved changes
markDirty(): void
// Check whether the workbook has unsaved changes
isDirty(): booleanPerformance
Built-in rolling-window telemetry tracks 6 metrics with P95, mean, and max statistics.
// Snapshot all current performance metrics
getPerfSnapshot(): PerfSnapshot
// Reset all rolling windows before starting a benchmark
resetPerfCounters(): voidPerfSnapshot shape:
interface PerfSnapshot {
renderMs: { p95: number; mean: number; max: number };
paintMs: { p95: number; mean: number; max: number };
scrollFps: { p95: number; mean: number; max: number };
inputLatencyMs: { p95: number; mean: number; max: number };
formulaMs: { p95: number; mean: number; max: number };
estimatedCellMemoryKB: { p95: number; mean: number; max: number };
}Example:
sheet.resetPerfCounters();
// ... run your workload ...
const snap = sheet.getPerfSnapshot();
console.log('P95 render time:', snap.renderMs.p95, 'ms');
console.log('Mean scroll FPS:', snap.scrollFps.mean);
console.log('Formula P95:', snap.formulaMs.p95, 'ms');Lifecycle
// Attach an event listener
on(event: string, callback: (...args: any[]) => void): void
// Remove an event listener
off(event: string, callback: (...args: any[]) => void): void
// Emit an event manually
emit(event: string, ...args: any[]): boolean
// Destroy the instance — unmounts from DOM and releases the Web Worker
destroy(): voidEvent System
Register listeners with sheet.on(eventName, handler). All events pass the active WorksheetModel as the first argument.
Core Events
| Event | Callback Signature | Description |
| :--------------- | :-------------------------------------------------------------------------- | :-------------------------------------------- |
| onload | (ws: WorksheetModel) => void | Fired after the spreadsheet is fully mounted. |
| onchange | (ws, cell, x: number, y: number, value: string, oldValue: string) => void | Cell value changed. |
| onselection | (ws, x1: number, y1: number, x2: number, y2: number) => void | Selection range changed. |
| oncreate | (ws: WorksheetModel) => void | A new sheet was created. |
| oneditionstart | (ws, x: number, y: number) => void | Cell edit mode started. |
| oneditionend | (ws, x: number, y: number, value: string) => void | Cell edit mode committed. |
| oneditioninput | (ws, x: number, y: number, value: string) => void | Keystroke inside the cell editor. |
| onfocus | (ws: WorksheetModel) => void | Spreadsheet received focus. |
| onblur | (ws: WorksheetModel) => void | Spreadsheet lost focus. |
| onundo | (ws: WorksheetModel) => void | Undo was performed. |
| onredo | (ws: WorksheetModel) => void | Redo was performed. |
Structural Events
| Event | Callback Signature | Description |
| :--------------- | :----------------------------------------------- | :---------------- |
| oninsertrow | (ws, rowIndex: number, count: number) => void | Rows inserted. |
| ondeleterow | (ws, rowIndex: number, count: number) => void | Rows deleted. |
| oninsertcolumn | (ws, colIndex: number, count: number) => void | Columns inserted. |
| ondeletecolumn | (ws, colIndex: number, count: number) => void | Columns deleted. |
| onmoverow | (ws, from: number, to: number) => void | Row moved. |
| onmovecolumn | (ws, from: number, to: number) => void | Column moved. |
| onresizerow | (ws, rowIndex: number, height: number) => void | Row resized. |
| onresizecolumn | (ws, colIndex: number, width: number) => void | Column resized. |
Style & Merge Events
| Event | Callback Signature | Description |
| :---------- | :-------------------------------------------------------------- | :------------------ |
| onstyle | (ws, x: number, y: number, style: Partial<CellStyle>) => void | Cell style changed. |
| onmerge | (ws, x1, y1, x2, y2: number) => void | Cells merged. |
| onunmerge | (ws, x1, y1, x2, y2: number) => void | Cells unmerged. |
Clipboard Events
| Event | Callback Signature | Description |
| :-------- | :----------------------------------------------------- | :-------------------------- |
| oncopy | (ws, selection: SelectionRange) => void | Data copied to clipboard. |
| onpaste | (ws, x: number, y: number, data: string[][]) => void | Data pasted from clipboard. |
Formula Events
| Event | Callback Signature | Description |
| :---------------- | :---------------------------------------------------- | :---------------------- |
| onformulachange | (ws, x: number, y: number, formula: string) => void | A formula cell changed. |
Event Usage Example
// Listen for value changes
sheet.on('onchange', (ws, cell, x, y, value, oldValue) => {
console.log(`[${x},${y}]: "${oldValue}" → "${value}"`);
});
// Listen for selection changes
sheet.on('onselection', (ws, x1, y1, x2, y2) => {
console.log(`Selection: [${x1},${y1}]:[${x2},${y2}]`);
});
// Listen for edit start/end
sheet.on('oneditionstart', (ws, x, y) => {
console.log(`Editing cell [${x},${y}]`);
});
sheet.on('oneditionend', (ws, x, y, value) => {
console.log(`Committed [${x},${y}] = ${value}`);
});
// Remove a listener
const handler = (ws: any) => console.log('undone');
sheet.on('onundo', handler);
sheet.off('onundo', handler);Key Types
/** Zero-based cell address */
interface CellAddress {
x: number; // column index (0 = A)
y: number; // row index (0 = row 1)
}
/** Inclusive selection rectangle */
interface SelectionRange {
x1: number;
y1: number; // top-left
x2: number;
y2: number; // bottom-right
}
/** Per-cell visual style */
interface CellStyle {
bold?: boolean;
italic?: boolean;
underline?: boolean;
strikethrough?: boolean;
fontSize?: number;
fontFamily?: string;
color?: string; // CSS hex e.g. '#FF0000'
backgroundColor?: string;
align?: 'left' | 'center' | 'right';
verticalAlign?: 'top' | 'middle' | 'bottom';
wrap?: boolean;
border?: BorderObject;
numberFormat?: string; // e.g. '#,##0.00', '$#,##0'
}
/** Sheet tab configuration */
interface SheetMeta {
worksheetName: string;
minDimensions?: [cols: number, rows: number];
frozenRows?: number;
frozenCols?: number;
hidden?: boolean;
}Architecture Notes
| Component | File | Responsibility |
| :-------------------- | :--------------------------------- | :------------------------------------------- |
| SpreadsheetEngine | engine/SpreadsheetEngine.ts | Top-level public API owner |
| WorksheetModel | model/Worksheet.ts | Per-sheet state |
| SheetData | model/SheetData.ts | Sparse cell storage via ChunkedCellStore |
| ChunkedCellStore | model/chunks/ChunkedCellStore.ts | 256×256 block cell store |
| LRUCache | model/chunks/LRUCache.ts | Generic O(1) LRU eviction |
| Viewport | render/viewport/Viewport.ts | Canvas renderer; dirty-tile partial repaints |
| DisplayCacheManager | model/display-cache/ | LRU-bounded render cache (max 100K entries) |
| CalcWorkerHost | workers/calc/CalcWorkerHost.ts | Main-thread proxy to the formula Web Worker |
| SparseMetricsIndex | model/metrics/ | Fenwick-tree backed row/col offset index |
| MergeIndex | model/merges/ | Row-bucketed merge lookup |
| PerformanceMonitor | perf/PerformanceMonitor.ts | 6-metric rolling-window telemetry |
Invariants enforced by the architecture:
- No O(totalRows) or O(totalCols) work inside scroll or render handlers.
- Formula evaluation never happens on the paint thread.
_onViewChange(x, y)triggers a partial tile repaint;_onViewChange(-1, -1)triggers a full repaint.- Display cache is evicted proactively on scroll to keep memory footprint bounded.
License
Copyright © 2024-present Worksheet Systems. All rights reserved.
This software is proprietary. Usage is subject to the terms of the End-User License Agreement (EULA).
For licensing and technical support: [email protected]
