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

virtual-tree-canvas

v0.5.0

Published

High-performance Canvas2D virtual tree/table widget for very large hierarchical datasets.

Readme

virtual-tree-canvas

A fast, framework-agnostic tree and tree-table for large hierarchical data.

virtual-tree-canvas gives applications the interactions users expect from a native tree view—expandable branches, columns, selection, search, filtering and keyboard navigation—without creating a DOM node for every row. It renders only what is visible with Canvas2D, so it stays responsive as data and live updates grow.

Editable model inspector built with virtual-tree-canvas

Why use it?

  • Built for large trees. Only visible rows plus a small overscan range are painted.
  • Interactive by default. Tree-table columns, sorting, resizing, selection, focus, tooltips and keyboard navigation are included.
  • Good for live data. Batched state patches update status, progress and values without rebuilding the tree.
  • Framework-free. Use it directly from browser JavaScript; no React or Web Component runtime is required.
  • Easy to style. Built-in dark, light and tactical themes, type-based styling, and editable SVG icons.
  • Ready for inspectors. Turn plain JavaScript objects into compact editable forms or property tables.

Install

npm install virtual-tree-canvas

Quick start

<canvas id="assets-tree"></canvas>
#assets-tree {
  display: block;
  width: 100%;
  height: 480px;
}
import { TreeViewController } from 'virtual-tree-canvas';

const tree = new TreeViewController({
  canvas: document.querySelector('#assets-tree'),
  initialExpandDepth: 2,
});

tree.setData([
  { id: 'fleet', label: 'Fleet', type: 'root' },
  { id: 'radar-1', parentId: 'fleet', label: 'Forward radar', type: 'sensor' },
  { id: 'track-100', parentId: 'radar-1', label: 'Track T-100', type: 'track' },
]);

tree.setDynamicState([
  { id: 'radar-1', state: { status: 0, progress: 0.72 } },
  { id: 'track-100', state: { status: 1, value: 430 } },
]);

tree.render();

The controller observes the canvas size. Call tree.render() from your own animation loop when the data is live, or after changing data in a static view.

Virtual tree-table with status, progress and SVG icons

Common tasks

// Navigate and select
tree.expand('radar-1');
tree.focusNode('track-100', { align: 'center', select: true });
tree.setSelection(['track-100']);

// Search and filter
tree.search('forward radar');
tree.setFilter('track');
tree.clearFilter();

// Configure the tree-table
tree.setColumns(columns);
tree.sortBy('status', 'asc');
tree.resizeColumn('name', 320);

// Use a worker for expensive search/filter work
await tree.enableWorkers();

Live updates and performance

Use setDynamicState() for values that change frequently. It updates the dynamic state of affected nodes while retaining indexes, expansion state and visible rows.

tree.setDynamicState([
  { id: 'track-100', state: { status: 1, progress: 0.7, value: 42 } },
]);

setData(), expand/collapse, sorting and filtering are structural operations and may rebuild the visible row list. For very large datasets, enableWorkers() moves search and filtered row rebuilds off the main thread when Workers are available.

Columns

tree.setColumns([
  {
    id: 'name',
    label: 'Name',
    width: 340,
    minWidth: 160,
    kind: 'tree',
    value: (node) => node.label ?? node.id,
  },
  {
    id: 'status',
    label: 'Status',
    width: 84,
    minWidth: 64,
    align: 'center',
    kind: 'status',
    value: (_node, state) => state.status,
  },
  {
    id: 'progress',
    label: 'Progress',
    width: 140,
    kind: 'progress',
    value: (_node, state) => state.progress,
  },
]);

Available column kinds are tree, status, value, progress, type, updated and text. A column can also provide render(ctx, cell) for custom Canvas2D content. Import builtInColumns or defaultTreeTableColumns to start from the default set.

Themes and icons

Three themes are included:

import { darkTheme, lightTheme, tacticalTheme } from 'virtual-tree-canvas';

tree.setTheme(tacticalTheme);

Themes can map domain types to a colour and icon:

tree.setTheme({
  rowHeight: 28,
  font: '12px system-ui',
  types: {
    root: { icon: 'folder', color: '#38bdf8' },
    platform: { icon: 'aircraft', color: '#60a5fa' },
    sensor: { icon: 'radar', color: '#34d399' },
    warning: { icon: 'warning', color: '#facc15' },
  },
});

Built-in icons are editable SVG files in src/assets/icons. They are preloaded and rasterized once per icon, colour, CSS size and device pixel ratio. Rendering rows then uses a cached drawImage, not SVG parsing or path drawing.

Register application icons from an SVG URL, inline SVG or an image:

tree.registerIcon('camera', '/icons/camera.svg');
tree.registerIcon('satellite', '<svg viewBox="0 0 24 24"><path fill="currentColor" d="..."/></svg>');
tree.registerIcon('logo', imageElement);

Use currentColor in an SVG to inherit the type or dynamic-state colour. tree.iconRegistry.prepare({ icons, size, color, pixelRatio }) is also available when an application wants to warm a known icon set before display.

Model inspector

setModel() renders JSON-like data as an editable inspector. Metadata controls the editor, bounds, choices, labels and descriptions.

tree.setModel(
  {
    sensor: { enabled: true, range: 72, mode: 'track' },
    tracks: [{ id: 'T-100', speed: 430 }],
  },
  {
    'sensor.range': { min: 0, max: 120, step: 1, integer: true },
    'sensor.mode': { options: { Search: 'search', Track: 'track' } },
    'tracks.*.speed': { min: 0, max: 900, step: 5, unit: 'm/s', precision: 2 },
    'tracks.*.id': { readonly: true },
  },
  { presentation: 'pane', flatRoot: true },
);

Choose presentation: 'pane' for a compact folder-and-controls view, or presentation: 'table' for Property / Value / Type / Description columns. Editors are inferred from data and metadata: checkbox, range, number, text, select, colour, button, object and array.

Numeric metadata supports unit (a display-only string suffix) and precision (decimal places, 0–20). The unit has a reserved slot beside the number in both pane and table presentations, including range sliders. It stays visible while editing; the editor receives the full original numeric value and emits a number. Precision changes display only, not stored values, limits or step size. Omit both options to keep the existing formatting. Invalid precision values are ignored. Customize the suffix with theme.unitFont and theme.colors.unit (defaults to textMuted). Long units are truncated to fit the available cell width.

Listen for user edits and actions with valuechange, modelchange and action events.

Events

tree.on('nodeclick', (event) => {
  console.log(event.detail.nodeId);
});
tree.on('selectionchange', (event) => {});
tree.on('expand', (event) => {});
tree.on('searchchange', (event) => {});
tree.on('filterchange', (event) => {});
tree.on('valuechange', (event) => {});

Other events include nodehover, nodedblclick, collapse, focuschange, sortchange, columnschange, viewportchange, modelchange and action. The payload is always available as event.detail.

API overview

tree.setData(nodes);
tree.setDynamicState(patches);
tree.setTheme(theme);
tree.setColumns(columns);

tree.expand(nodeId);
tree.collapse(nodeId);
tree.toggle(nodeId);
tree.expandAll();
tree.collapseAll();

tree.search(query, { caseSensitive, wholeWord });
tree.setFilter(queryOrPredicate, { caseSensitive, wholeWord });
tree.clearSearch();
tree.clearFilter();

tree.focusNode(nodeId, { align: 'start' | 'center' | 'end' | 'nearest' });
tree.scrollToNode(nodeId, 'center');
tree.scrollTo(x, y);

tree.setSelection(ids);
tree.getSelection();
tree.registerIcon(name, svgOrImage);
tree.enableWorkers();
tree.disableWorkers();

Demo

npm run demo

Open http://localhost:4173/demo/ for the large-tree benchmark, or http://localhost:4173/demo/inspector.html for the editable inspector demo.

Tests

npm test