Canvas-Sheet
A lightweight, high-performance spreadsheet component built on the HTML5 Canvas API for modern web applications. Unlike other canvas-based spreadsheet libraries, Canvas-Sheet uses a schema-based approach that gives you strong typing, validation, and custom editors for each data type.
Demo: https://admin-remix.github.io/canvas-sheet
Features
- Schema-based data model with typed columns, validation, and field-specific configuration
- Canvas-based rendering for superior performance with large datasets
- Virtual scrolling to efficiently handle thousands of rows
- Multiple data types including text, number, date, boolean, and select/dropdown
- Cell editing with type-specific editors
- Selection and range operations (select cells, rows, copy/paste)
- Keyboard navigation for efficient data entry
- Resizable rows and columns
- Customizable styling with numerous appearance options
- Zero dependencies - pure JavaScript implementation
Installation
npm install canvas-sheet
Basic Usage
<div id="spreadsheet-container" style="width: 100%; height: 500px;"></div>
import { Spreadsheet } from 'canvas-sheet';
import "canvas-sheet/dist/spreadsheet.css";
const schema = {
id: { type: "number", decimal: false, label: "ID" },
name: {
type: "text",
required: true,
maxlength: 50,
label: "Full Name",
},
email: {
type: "email",
required: true,
label: "Email Address",
},
isActive: { type: "boolean", label: "Active" }
};
const data = [
{ id: 1, name: "John Doe", email: "john@example.com", isActive: true },
{ id: 2, name: "Jane Smith", email: "jane@example.com", isActive: false },
{ id: 3, name: "Bob Johnson", email: "bob@example.com", isActive: true }
];
const spreadsheet = new Spreadsheet(
"spreadsheet-container",
schema,
data,
{
headerHeight: 40,
defaultRowHeight: 36,
font: "14px Arial"
}
);
Advanced Examples
Using Dropdown/Select Fields
You can dynamically control which cells are disabled based on row data
const schema = {
status: {
type: "select",
label: "Status",
values: [
{ id: 1, name: "Active" },
{ id: 2, name: "Pending" },
{ id: 3, name: "Inactive" }
],
disabled: (rowData, rowIndex)=>{
console.log('Row index', rowIndex);
return rowData.isRestricted && rowData.locationId === 1
},
}
};
Dynamic Data Updates
const currentData = spreadsheet.getData();
spreadsheet.setData(newData);
spreadsheet.updateCell(rowIndex, 'fieldName', newValue);
Cell Update and Selection Callbacks
You can implement custom logic when cells are updated, including adding loading states and validation:
const spreadsheet = new Spreadsheet(
"spreadsheet-container",
schema,
data,
{
onCellsUpdate: (rows) => {
for (const row of rows) {
if (row.columnKeys.includes('email') && row.data.email && row.data.email.endsWith('@sample.net')) {
spreadsheet.updateCell(row.rowIndex, 'loading:email', true);
setTimeout(() => {
spreadsheet?.updateCells([
{ rowIndex: row.rowIndex, colKey: 'loading:email', value: null },
{ rowIndex: row.rowIndex, colKey: 'error:email', value: `Account ${row.data.email} does not exist` }
]);
}, 2000);
}
}
},
onCellSelected: (rowIndex, colKey, rowData) => {
console.log('Selected', rowIndex, colKey, rowData[colKey]);
},
}
);
Custom Date Picker Support
The native date picker is used by default. Enable the customDatePicker
option which will trigger the onEditorOpen
callback each time a date field is opened. When user selects a date from your custom date picker, call the setValueFromCustomEditor
method to set the value of the cell which restores focus to the spreadsheet automatically.
let spreadsheet;
let selectedCellForEditor;
function openDatePicker(rowIndex: number, colKey: string, rowData: DataRow, bounds: CellBounds) {
selectedCellForEditor = { rowIndex, colKey, rowData, bounds };
const selectedDate = rowData[colKey];
const positionX = bounds.x;
const positionY = bounds.y;
const width = bounds.width;
const height = bounds.height;
console.log('open custom date picker', selectedDate, "at", {
positionX,
positionY,
width,
height
});
}
function closeDatePicker(value: string) {
spreadsheet?.setValueFromCustomEditor(selectedCellForEditor.rowIndex, selectedCellForEditor.colKey, value);
selectedCellForEditor = null;
}
spreadsheet = new Spreadsheet(
"spreadsheet-container",
schema,
data,
{
customDatePicker: true,
onEditorOpen: (rowIndex: number, colKey: string, rowData: DataRow, bounds: CellBounds) => {
openDatePicker(rowIndex, colKey, rowData, bounds);
},
}
);
Configuration Options
Canvas-Sheet is highly customizable with many options:
const options = {
defaultColumnWidth: 120,
defaultRowHeight: 30,
minColumnWidth: 50,
maxColumnWidth: 500,
minRowHeight: 25,
maxRowHeight: 100,
headerHeight: 36,
rowNumberWidth: 50,
font: '14px Arial',
headerFont: 'bold 14px Arial',
textColor: '#333',
cellBgColor: '#ffffff',
activeCellBgColor: '#edf3ff',
selectedRowBgColor: '#f5f8ff',
selectedRangeBgColor: '#e8f0ff',
headerTextColor: '#333',
headerBgColor: '#f5f5f5',
gridLineColor: '#e0e0e0',
textAlign: 'left',
padding: 8,
verbose: false
customDatePicker: false,
onEditorOpen: (rowIndex, colKey, rowData, bounds) => void,
onColumnDelete: (colIndex, schema) => void,
onRowDeleted: (rows) => void,
onCellSelected: (rowIndex, colKey, rowData) => void,
onCellsUpdate: (rows) => void,
};
Column Schema Options
Each column can have type-specific configuration:
const schema = {
name: {
type: "text",
required: true,
maxlength: 50,
label: "Name"
},
amount: {
type: "number",
decimal: true,
label: "Amount"
},
isActive: {
type: "boolean",
label: "Active"
},
createdAt: {
type: "date",
label: "Created Date"
},
status: {
type: "select",
label: "Status",
values: [
{ id: 1, name: "Active" },
{ id: 2, name: "Pending" },
{ id: 3, name: "Inactive" }
],
disabled: (data)=>{
return data.status === 3 && !data.isActive;
}
}
};
Events and Interaction
Canvas-Sheet handles many events automatically:
- Click to select cells
- Double-click/tab/enter to edit cells
- Click and drag or shift click to select ranges
- Keyboard navigation (arrow keys, tab, enter, escape)
- Copy/paste support
- Column/row resizing
- Press delete on a column header to delete the column (if removable is true)
- Press delete on selected rows to delete the rows
Browser Support
Canvas-Sheet works in all modern browsers that support HTML5 Canvas.
License
MIT