arthub-table
v0.2.4
Published
High-performance canvas-based table/grid component for Vue 3 with TypeScript support, featuring virtual scrolling, cell viewers, grouped rows, and nested grids.
Maintainers
Readme
arthub-table
High-performance canvas-based table/grid component for Vue 3 with TypeScript support.
Features
- 🚀 Canvas Rendering: High-performance rendering engine using HTML5 Canvas
- 📊 Virtual Scrolling: Efficiently handles large datasets with smooth scrolling
- 🎨 20+ Built-in Cell Viewers: Text, Select, Image, Person, Progress, Status, DateTime, Boolean, and more
- 🔌 Extensible Viewer System: Register custom cell viewers through the Viewer Registry
- 📦 Grouped Tables: Multi-level collapsible group headers
- 📐 Nested Grids: Support for nested table within cells
- 📋 Clipboard: Copy, paste, and clear cell content
- ✏️ Cell Editing: Inline editing with validation support
- 🔒 Fixed Columns: Pin columns to left or right
- 📏 Resizable Columns: Drag to resize column widths
- 🔄 Drag & Drop: Reorder rows and columns via drag and drop
- 🎯 Cell Selection: Single and multi-cell selection with keyboard navigation
- ✅ Validation: Built-in cell validation with custom rules
- 🌗 Theming: Light/dark theme support with custom style registration
- 📱 TypeScript: Full TypeScript type definitions
Installation
npm install arthub-tableQuick Start
<template>
<DataGrid
:columns="columns"
:data="data"
:width="800"
:height="400"
@after-edit-cell="handleEdit"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { DataGrid } from "arthub-table";
import type { ColumnConfig } from "arthub-table";
const columns = ref<ColumnConfig[]>([
{ title: "Name", key: "name", width: 200, type: "text" },
{ title: "Age", key: "age", width: 100, type: "number" },
{ title: "Email", key: "email", width: 300, type: "text" },
]);
const data = ref([
{ id: 1, name: "Alice", age: 28, email: "[email protected]" },
{ id: 2, name: "Bob", age: 32, email: "[email protected]" },
]);
const handleEdit = (editData: any) => {
console.log("Cell edited:", editData);
};
</script>Using Cell Viewers
Cell viewers provide rich data rendering within cells:
<script setup lang="ts">
import { DataGrid, registerDefaultViewers } from "arthub-table";
import type { ColumnConfig } from "arthub-table";
// Register all built-in viewers (call once at app startup)
registerDefaultViewers();
const columns = ref<ColumnConfig[]>([
{ title: "Task", key: "task", width: 200, type: "text", useViewer: true },
{
title: "Status",
key: "status",
width: 120,
type: "status",
useViewer: true,
options: [
{ label: "To Do", value: "todo", color: "#909399" },
{ label: "Done", value: "done", color: "#67C23A" },
],
},
{
title: "Progress",
key: "progress",
width: 180,
type: "progress",
useViewer: true,
},
{
title: "Assignee",
key: "assignee",
width: 150,
type: "person",
useViewer: true,
},
]);
</script>Available Viewer Types
| Type | Description | Data Type |
| --------------------- | ------------------------- | ----------------------------- |
| text | Plain text display | TextViewerData |
| select | Dropdown select with tags | SelectViewerData |
| image | Image display | ImageViewerData |
| person | User avatar and name | PersonViewerData |
| progress | Progress bar (draggable) | ProgressViewerData |
| datetime | Date/time display | DatetimeViewerData |
| boolean | Checkbox toggle | BooleanViewerData |
| status | Status badge | StatusViewerData |
| hyperlink | Clickable link | HyperlinkTextViewerData |
| file | File preview | FileViewerData |
| module | Module tags | ModuleViewerData |
| table-action-button | Action buttons | TableActionButtonViewerData |
| nested | Nested grid | NestedGridViewerData |
| group-header | Group header row | GroupHeaderViewerData |
API
DataGrid Props
| Prop | Type | Default | Description |
| ---------------- | ---------------- | ----------------- | --------------------------- |
| rowKey | string | 'id' | Row unique identifier field |
| width | number | - | Table width in pixels |
| height | number | - | Table height in pixels |
| columns | ColumnConfig[] | [] | Column definitions |
| data | any[] | [] | Table data array |
| datePattern | string | 'absolute_date' | Date display type |
| customAutofill | Function | - | Custom autofill handler |
DataGrid Events
| Event | Payload | Description |
| ------------------------ | ------------------------------------------ | ------------------------------- |
| after-edit-cell | data | Fired after a cell is edited |
| after-autofill | data | Fired after autofill |
| after-paste | data | Fired after paste |
| after-clear | data | Fired after clear |
| on-load | - | Fired when grid is loaded |
| on-cell-text-click | rowData, colId | Fired when cell text is clicked |
| on-group-toggle | groupRow, expanded | Fired when group is toggled |
| on-row-drag-reorder | draggedRows, targetIndex | Fired after row drag reorder |
| on-column-drag-reorder | draggedColIds, targetColId, insertBefore | Fired after column reorder |
DataGrid Methods (via ref)
| Method | Description |
| ------------------------------ | ------------------------------ |
| reload() | Resize and redraw |
| loadData(data) | Load new data |
| getData() | Get current data |
| getCheckedRows() | Get checked rows |
| getChangedRows() | Get changed rows |
| validate(callback) | Validate all cells |
| setTheme(theme) | Set theme ('light' or 'dark') |
| setStriped(enabled) | Enable/disable striped rows |
| showLoading(rowIds, colKeys) | Show loading on specific cells |
| hideLoading(rowIds, colKeys) | Hide loading on specific cells |
| setSelectedRows(rowIds) | Set selected rows |
TypeScript Types
arthub-table exports comprehensive TypeScript types:
import type {
ColumnConfig,
CellData,
RowData,
HeaderConfig,
// Viewer types
CellViewer,
CellViewerData,
TextViewerData,
SelectViewerData,
ProgressViewerData,
// Group types
GroupRowData,
GroupedTableRowData,
} from "arthub-table";Local Development (with AssetMatrix)
Using webpack alias
- Create
.env.localin AssetMatrix project root:
USE_LOCAL_CANVAS_TABLE=true
LOCAL_CANVAS_TABLE_PATH=../arthub-table- Start arthub-table dev server:
cd arthub-table
npm run serve- Start AssetMatrix with local arthub-table:
cd AssetMatrix
npm run dev:local-canvasUsing npm link
# In arthub-table directory
npm link
# In AssetMatrix directory
npm link arthub-tableBuilding
# Build library (CommonJS + UMD + types)
npm run build
# Build demo site
npm run build:demo
# Build only type declarations
npm run build:typesPublishing
# Bump version and publish
npm version patch # or minor, or major
npm publishOr push a version tag to trigger CI/CD:
git tag v1.0.1
git push origin v1.0.1Table CRUD Operations — Best Practices
This section documents how to perform Create, Read, Update, Delete operations on cells, rows, and columns using the DataGrid component.
Core Concepts
| Concept | Description |
| ---------------------- | ---------------------------------------------------------------------------------------- |
| ref="datagrid" | The Vue template ref to access the DataGrid component instance |
| datagrid.value?.grid | The underlying core DataGrid engine (for advanced low-level API) |
| loadData(data) | Re-render all rows with new data; resets editor and selection state |
| reload() | Recalculate table dimensions and scroll positions (after structural changes) |
| markDirty() | Lightweight: flags the canvas for repaint on the next animation frame |
| Vue Watcher | Modifying columns or data refs automatically triggers loadColumns() + loadData() |
1. Update a Single Cell
Use grid.setData(value, { colIndex, rowIndex }) to write a value into a specific cell by its column and row index.
const grid = (datagrid.value as any)?.grid;
// Update cell at row 0, column 2
grid.setData("New Value", { colIndex: 2, rowIndex: 0 });
// Trigger canvas repaint
grid.markDirty();Note:
setData()respectsreadonlycolumns. The cell'soriginalValueis preserved for change tracking.
2. Update an Entire Column
Loop through all data rows and call cell.setData() on each cell object:
const grid = (datagrid.value as any)?.grid;
const colIndex = 2; // Target column index
gridData.value.forEach((_row, rowIndex) => {
if (_row.isGroup || _row.isSeparateRow) return; // Skip group/separator rows
const cell = grid.body.getCell(colIndex, rowIndex);
if (cell) {
cell.setData(`Col_Value_${rowIndex}`);
}
});
grid.markDirty();3. Batch Update a Rectangular Area
Use grid.batchSetData({ colIndex, rowIndex, value }) where value is a 2D array:
const grid = (datagrid.value as any)?.grid;
// Write a 3-row × 2-column block starting at [row=0, col=1]
grid.batchSetData({
colIndex: 1,
rowIndex: 0,
value: [
["A1", "B1"],
["A2", "B2"],
["A3", "B3"],
],
});
grid.markDirty();4. Update Rows by rowKey Match (Partial Update)
Use updateData(data) to merge updates into existing rows matched by the rowKey field (default "id"):
datagrid.value?.updateData([
{ id: 1, emp_name: "Updated Name 1", emp_no: "001" },
{ id: 3, job_name: "Updated Job 3" },
]);Behavior: Only the fields you provide will be updated. Other fields in the row are unchanged. This does NOT trigger a full reload; it updates in-place.
5. Refresh the Visible Area
There are two levels of refresh:
| Method | When to Use |
| -------------------------- | ------------------------------------------------------------------------------------------------- |
| grid.markDirty() | After data-only changes (cell values). Lightweight, uses requestAnimationFrame. |
| datagrid.value?.reload() | After structural changes (columns added/removed, row heights changed). Full resize + recalculate. |
// Lightweight repaint
const grid = (datagrid.value as any)?.grid;
grid.markDirty();
// Full reload (after structural changes)
await nextTick();
datagrid.value?.reload();6. Add a Row
Append a new row object to the data array and call loadData():
const newRow = {
_rowId: `row_${Date.now()}`,
id: gridData.value.length + 1,
};
// Dynamically fill all column keys with default values
for (const col of columns.value) {
if (col.key && !(col.key in newRow)) {
newRow[col.key] = col.type === "number" ? 0 : "";
}
}
gridData.value.push(newRow);
datagrid.value?.loadData(gridData.value);Grouped mode: Insert the row at the correct position within
groupedDataCache, then filter and reload. See theonAddRow()handler inApp.vuefor a complete example.
7. Delete a Row
Remove the row from the data array and reload:
const indexToDelete = gridData.value.findIndex(
(row) => !row.isGroup && !row.isSeparateRow,
);
if (indexToDelete >= 0) {
gridData.value.splice(indexToDelete, 1);
datagrid.value?.loadData(gridData.value);
}8. Add a Column
Add a new column definition to the columns ref. The Vue watcher will automatically trigger loadColumns() + loadData():
const newCol = {
title: "New Column",
key: "new_col",
size: "small",
align: "left",
};
// Insert at a specific position
const newColumns = [...columns.value];
newColumns.splice(insertIndex, 0, newCol);
columns.value = newColumns;
// Fill data for the new column
gridData.value.forEach((row, i) => {
if (!row.isGroup) {
row[newCol.key] = `Value_${i}`;
}
});9. Delete a Column
Remove the column from the columns ref:
const colIndexToDelete = columns.value.findIndex(
(col) => col.key === "target_key",
);
if (colIndexToDelete >= 0) {
const newColumns = [...columns.value];
newColumns.splice(colIndexToDelete, 1);
columns.value = newColumns;
}10. Read Data
| Method | Returns | Description |
| --------------------------------------- | ----------- | --------------------------------------- |
| datagrid.value?.getData() | RowData[] | All row data with merged cell values |
| datagrid.value?.getCheckedRows() | RowData[] | Rows where checkbox is checked |
| datagrid.value?.getChangedRows() | RowData[] | Rows that have been modified since load |
| grid.body.getRowData(rowIndex) | RowData | A single row by index |
| grid.body.getCell(colIndex, rowIndex) | Cell | A single cell object |
// Get all data
const allData = datagrid.value?.getData();
// Get changed rows only
const changedRows = datagrid.value?.getChangedRows();
// Get a single cell's value
const grid = (datagrid.value as any)?.grid;
const cell = grid.body.getCell(2, 0);
console.log(cell.value); // Current cell value11. Validation
// Validate all cells
datagrid.value?.validate((valid) => {
if (!valid) {
const errors = datagrid.value?.getValidations();
console.log("Validation errors:", errors);
}
});
// Validate only changed rows
datagrid.value?.validateChanged((valid) => {
/* ... */
});
// Set external validation errors (matched by rowKey)
datagrid.value?.setValidations([
{ id: 1, emp_name: "Name is required", emp_no: "Invalid number" },
{ id: 3, job_name: "Job name too long" },
]);
// Clear all validations
datagrid.value?.clearValidations();12. Undo / Redo
Built-in keyboard shortcuts:
| Shortcut | Action |
| ------------------------ | -------------- |
| Ctrl+Z / Cmd+Z | Undo last edit |
| Ctrl+Y / Cmd+Shift+Z | Redo last undo |
History is automatically tracked for
doneEdit(),pasteData(),autofillData(), andclearSelectedData()operations. Max 20 history entries.
Complete Example
See App.vue for interactive demonstrations of all these patterns. The toolbar includes buttons for:
- ✏️ 更新单个格子 — Single cell update via
grid.setData() - 📝 更新整列 — Column-wide update via
cell.setData()loop - 📋 批量更新区域 — Batch area update via
grid.batchSetData() - 🔄 刷新可见区域 — Canvas repaint via
grid.markDirty() - ➕ 新增一行 / ➕ 新增一列 — Dynamic row/column addition
- 🗑️ 删除首行 / 🗑️ 删除末列 — Row/column deletion
