
Security News
MCP Community Begins Work on Official MCP Metaregistry
The MCP community is launching an official registry to standardize AI tool discovery and let agents dynamically find and install MCP servers.
canvas-sheet
Advanced tools
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 custo
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
npm install canvas-sheet
<div id="spreadsheet-container" style="width: 100%; height: 500px;"></div>
import { Spreadsheet } from "canvas-sheet";
// basic input and dropdown styles
import "canvas-sheet/dist/spreadsheet.css";
// Define the schema for your spreadsheet
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" },
};
// Your data array
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 },
];
// Initialize the spreadsheet
const spreadsheet = new Spreadsheet(
"spreadsheet-container", // Container ID
schema, // Column schema
data, // Initial data
{
// Optional configuration
headerHeight: 40,
defaultRowHeight: 36,
font: "14px Arial",
}
);
You can dynamically control which cells are disabled based on row data
const schema = {
// ... other fields
status: {
type: "select",
label: "Status",
values: [
{ id: 1, name: "Active" },
{ id: 2, name: "Pending" },
{ id: 3, name: "Inactive" },
],
// custom cell disabling logic
disabled: (rowData, rowIndex) => {
console.log("Row index", rowIndex);
return rowData.isRestricted && rowData.locationId === 1;
},
},
};
// Get current data
const currentData = spreadsheet.getData();
// Update the data
spreadsheet.setData(newData);
// Update a single cell
spreadsheet.updateCell(rowIndex, "fieldName", newValue);
You can implement custom logic when cells are updated, including adding loading states and validation:
const spreadsheet = new Spreadsheet("spreadsheet-container", schema, data, {
// ... other options
onCellsUpdate: (rows: CellUpdateEvent[]) => {
// Example: Show loading and then error state for email fields from a certain domain
for (const row of rows) {
if (
row.columnKeys.includes("email") &&
row.data.email &&
row.data.email.endsWith("@sample.net")
) {
// Set loading state on single cell
spreadsheet.updateCell(row.rowIndex, "loading:email", true);
// Simulate async validation
setTimeout(() => {
// update multiple cells at once which is more efficient than updating one by one
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 }: CellEvent) => {
console.log("Selected", rowIndex, colKey, rowData[colKey]);
},
// ... other options
});
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.
import { CellEventWithBounds, Spreadsheet } from "canvas-sheet";
let spreadsheet: Spreadsheet | null = null;
let selectedCellForEditor: CellEventWithBounds | null = null;
function openDatePicker(event: CellEventWithBounds) {
selectedCellForEditor = { ...event };
const selectedDate = event.rowData[event.colKey];
const positionX = event.bounds.x;
const positionY = event.bounds.y;
const width = event.bounds.width;
const height = event.bounds.height;
console.log("open custom date picker", selectedDate, "at", {
positionX,
positionY,
width,
height,
});
// or show a modal with a date picker
}
// call the "setValueFromCustomEditor" method to set the value of the cell after the date is selected
function closeDatePicker(value: string) {
spreadsheet?.setValueFromCustomEditor(
selectedCellForEditor.rowIndex,
selectedCellForEditor.colKey,
value
);
selectedCellForEditor = null;
}
spreadsheet = new Spreadsheet("spreadsheet-container", schema, data, {
// ... other options
customDatePicker: true,
onEditorOpen: (event: CellEventWithBounds) => {
openDatePicker(event);
},
// ... other options
});
Canvas-Sheet is highly customizable with many options:
const options = {
// Dimensions
defaultColumnWidth: 120,
defaultRowHeight: 30,
minColumnWidth: 50,
maxColumnWidth: 500,
minRowHeight: 25,
maxRowHeight: 100,
headerHeight: 36,
rowNumberWidth: 50,
// Styling
font: '14px Arial',
headerFont: 'bold 14px Arial',
textColor: '#333',
cellBgColor: '#ffffff',
activeCellBgColor: '#edf3ff',
selectedRowBgColor: '#f5f8ff',
selectedRangeBgColor: '#e8f0ff',
headerTextColor: '#333',
headerBgColor: '#f5f5f5',
gridLineColor: '#e0e0e0',
// Additional options
textAlign: 'left',
padding: 8,
verbose: false
// Custom date picker support
customDatePicker: false,
// when a date field is opened
onEditorOpen: (event: CellEventWithBounds) => void,
// when user presses delete on a column header, does not delete the column
// you have to call "removeColumnByIndex()" to delete the column
onColumnDelete: (colIndex: number, schema: ColumnSchema) => void,
// after rows are deleted
onRowDeleted: (rows: DataRow[]) => void,
// when a cell is selected
onCellSelected: (event: CellEvent) => void,
// when cells are updated
onCellsUpdate: (rows: CellUpdateEvent[]) => void,
};
Each column can have type-specific configuration:
const schema = {
// Text field with validation
name: {
type: "text",
required: true,
maxlength: 50,
label: "Name",
},
// Number field with options
amount: {
type: "number",
decimal: true, // Allow decimal values
label: "Amount",
},
// Boolean field (checkbox)
isActive: {
type: "boolean",
label: "Active",
defaultValue: true,
},
// Date field
createdAt: {
type: "date",
label: "Created Date",
defaultValue: new Date().toISOString().split("T")[0],
},
// Select/dropdown field
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;
},
},
};
Canvas-Sheet handles many events automatically:
Canvas-Sheet works in all modern browsers that support HTML5 Canvas.
MIT
FAQs
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 custo
We found that canvas-sheet demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The MCP community is launching an official registry to standardize AI tool discovery and let agents dynamically find and install MCP servers.
Research
Security News
Socket uncovers an npm Trojan stealing crypto wallets and BullX credentials via obfuscated code and Telegram exfiltration.
Research
Security News
Malicious npm packages posing as developer tools target macOS Cursor IDE users, stealing credentials and modifying files to gain persistent backdoor access.