npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

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-table

Quick 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

  1. Create .env.local in AssetMatrix project root:
USE_LOCAL_CANVAS_TABLE=true
LOCAL_CANVAS_TABLE_PATH=../arthub-table
  1. Start arthub-table dev server:
cd arthub-table
npm run serve
  1. Start AssetMatrix with local arthub-table:
cd AssetMatrix
npm run dev:local-canvas

Using npm link

# In arthub-table directory
npm link

# In AssetMatrix directory
npm link arthub-table

Building

# Build library (CommonJS + UMD + types)
npm run build

# Build demo site
npm run build:demo

# Build only type declarations
npm run build:types

Publishing

# Bump version and publish
npm version patch  # or minor, or major
npm publish

Or push a version tag to trigger CI/CD:

git tag v1.0.1
git push origin v1.0.1

Table 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() respects readonly columns. The cell's originalValue is 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 the onAddRow() handler in App.vue for 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 value

11. 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(), and clearSelectedData() 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

License

MIT