Elenweave
Components (HTML Nodes)
DocsComponents

Components (HTML nodes)

Components are type: "html" nodes that render real DOM in an overlay layer, while still participating in selection, linking, resizing, and serialization.

DOM overlay layer Registry / render / loader data-ew hooks SVG chart components
Austere systems reference

Overview

Elenweave supports HTML nodes that mount DOM on top of the canvas. This enables embedded UI (forms, pickers, widgets) without losing graph behaviors (selection, link, resize).

Rule: an HTML node is a normal graph node with type: "html" plus one of component, render, or loader.

How it works

The view creates a DOM overlay layer (.ew-html-layer) and mounts HTML nodes there. Each render pass, the view syncs nodes, repositions/scales DOM to match the camera, and remounts when the component identity changes.

  • HTML nodes are standard graph nodes with type: "html" plus component / render / loader.
  • Per render: add/remove nodes, align DOM with camera (translate + scale), and call update(node, api) when data/props change.
  • Component identity is derived from component, render, and loader. If any change, the view remounts the node.

Node fields used by components

The component system watches these node fields to decide what to mount and when to update.

  • type: "html" — opt into DOM mounting
  • component — registry key
  • render — inline renderer per node
  • loader — async resolver for a component definition
  • data / props — component state/config
Standard node fields still apply (position/size and visual state): x, y, w, h, color, pulse, and fixed.

Component contract

Render functions receive { node, api }. The render result can be an element, a lifecycle object, or an HTML string.

Render return types
  • HTMLElement
  • { el, update, cleanup }
  • string (treated as HTML and wrapped)

api helpers

  • setData(patch) / setProps(patch) — merge into node.data / node.props
  • updateNode(patch) — update any node fields
  • select() — select this node
  • emit(event, payload) — emit view events (e.g. node:input)
  • getNode() — return the latest node snapshot

Security & trust

Sanitize HTML/SVG and gate untrusted component code when embedding external content.

  • sanitizeHtml / sanitizeSvg default to true.
  • Provide domPurify (or sanitizer) to sanitize HTML strings and in-place elements.
  • Customize sanitizer behavior with sanitizeConfig: { html, svg }.
  • Mark registry entries as untrusted with trusted: false, or set node.trusted = false.
  • Set allowUntrustedComponents: false to block untrusted components.
Without DOMPurify, HTML strings are escaped; HTML element sanitization is skipped (warning logged). SVG markup falls back to a basic sanitizer.

Lifecycle

Mount, update, and unmount behavior is deterministic and derived from component identity.

  • Mount: when node appears or component identity changes, the view resolves and calls render.
  • Update: when identity is unchanged and the node is marked dirty, update(node, api) runs.
  • Unmount: when node is removed or remounted, cleanup() runs (if provided), then the element is removed.

Selection, drag, link, resize

HTML nodes are interactive by default. Use data-ew-* attributes to control selection and handles.

  • data-ew-select: marks a selectable area (root is selectable by default)
  • data-ew-drag: marks a drag handle region
  • data-ew-link: marks a link anchor for linking mode
  • Resize handles are injected automatically by the view.
  • Set node.fixed = true to disable dragging and resizing (handles hidden).
Input safety: interactive inputs (input, textarea, select, button, contenteditable) are protected so clicks won’t accidentally start linking.
Camera sync: the HTML layer is positioned in screen space and scaled to match panning/zooming. Resizing updates w/h and triggers redraw.

Events

The view is an event emitter. Components commonly emit node:input.

JavaScript
view.on('node:input', (payload) => {
  console.log('component input', payload);
});
Useful signals:
  • selection — node or null
  • edge:selection — edge or null
  • node:input — component-defined payload

Registering components

Register once on the view, then use component on nodes to mount.

ESM/CJS build (recommended)

JavaScript
import { ElenweaveWorkspace, ElenweaveView } from 'elenweave';
import { OptionPicker } from 'elenweave/components';
import 'elenweave/styles.css';

const workspace = new ElenweaveWorkspace();
const view = new ElenweaveView({ canvas, workspace });
view.registerComponent('OptionPicker', { render: OptionPicker });
JavaScript
view.registerComponent('option-picker', {
  render: ({ node, api }) => {
    // return HTMLElement
  }
});

// You can also pass a registry map when creating the view.

Dynamic components (async)

Load component code lazily via a loader in the registry or per-node.

JavaScript
view.registerComponent('lazy-widget', {
  loader: () => import('./lazy-widget.js')
});

// or per-node:
view.updateNode(id, { loader: () => import('./lazy-widget.js') });
The view awaits the loader, mounts when ready, and ignores stale loads if the node changed or was removed.

Capturing user input

Write state to the node and emit a typed signal.

JavaScript
api.setData({ selected: value });
api.emit('node:input', { id: node.id, value });
This keeps state on the node (data) and notifies listeners on the view.

Bulk updates

For large changes, update nodes/edges in bulk from the host app.

JavaScript
graph.updateNodes([{ id, patch: { text: 'Updated' } }]);
workspace.updateNodesBatch([
  { graphId, updates: [{ id, patch: { color: '#78dbff' } }] }
]);

Example: component with update + cleanup

Return an object with el, update, and cleanup to control lifecycle.

JavaScript
view.registerComponent('counter', {
  render: ({ node, api }) => {
    const el = document.createElement('div');
    el.className = 'counter';
    const btn = document.createElement('button');
    btn.textContent = node.data?.label || 'Click';
    btn.addEventListener('click', () => {
      const value = (node.data?.count || 0) + 1;
      api.setData({ count: value });
      api.emit('node:input', { id: node.id, value });
    });
    el.appendChild(btn);

    return {
      el,
      update: (nextNode) => {
        btn.textContent = `${nextNode.data?.count || 0}`;
      },
      cleanup: () => {
        btn.replaceWith(btn.cloneNode(true));
      }
    };
  }
});
Component identity is derived from component, render, and loader. Changing between them remounts the node.

Chart components

Chart components are regular HTML nodes that render SVG visuals in the overlay layer. They remain draggable, linkable, resizable, and serialize with the graph.

Available: LineChart, AreaChart, BarChart (stacked), ScatterChart, HeatmapChart, RadarChart, SparklineChart.

Shared fields

Charts read from node.data or node.props (both supported).

  • title: header title
  • palette: override series colors
  • labels: x-axis labels (line/area/bar)
  • legend: set false to hide legend
  • minX/maxX/minY/maxY: axis overrides (cartesian)

Data shapes

Line / Area / Bar

JavaScript
data: {
  series: [
    { name: 'Alpha', color: '#5feaff', values: [4, 6, 5, 8] },
    { name: 'Beta', values: [2, 4, 3, 5] }
  ],
  labels: ['Mon', 'Tue', 'Wed', 'Thu'],
  stacked: false // bar chart only
}

Scatter

JavaScript
data: {
  series: [
    { name: 'Cluster A', points: [{ x: 1, y: 2 }, { x: 2, y: 3 }] },
    { name: 'Cluster B', points: [{ x: 1.4, y: 4.2 }, { x: 2.6, y: 4.4 }] }
  ],
  radius: 3
}

Heatmap

JavaScript
data: {
  matrix: [
    [2, 4, 6],
    [3, 5, 7],
    [4, 6, 8]
  ],
  xLabels: ['A', 'B', 'C'],
  yLabels: ['Row 1', 'Row 2', 'Row 3'],
  min: 0,
  max: 10
}

Radar

JavaScript
data: {
  axes: ['Core', 'Flux', 'Drift', 'Pulse'],
  series: [
    { name: 'Alpha', values: [6, 8, 5, 7] },
    { name: 'Beta', values: [4, 6, 4, 5] }
  ],
  max: 10
}

Sparkline

JavaScript
data: {
  values: [3, 5, 4, 6, 5, 7, 6, 8],
  color: '#5feaff'
}

Usage example

Register the component, then add an HTML node with component set to the chart name.

JavaScript
view.registerComponent('LineChart', { loader: () => import('./components/chart-line.js') });

view.addNode({
  type: 'html',
  component: 'LineChart',
  x: 200, y: 160, w: 380, h: 240,
  props: { title: 'Signal Trend', labels: ['Mon', 'Tue', 'Wed', 'Thu'] },
  data: {
    series: [
      { name: 'Alpha', values: [4, 6, 5, 8] },
      { name: 'Beta', values: [2, 4, 3, 5] }
    ]
  }
});

Built-in system components

Built-in components live in canvas/components/ and mount as HTML nodes (type: "html"). They accept input and emit node:input.

JavaScript
view.registerComponent('OptionPicker', { loader: () => import('./components/option-picker.js') });

view.addNode({
  type: 'html',
  component: 'OptionPicker',
  x: 200, y: 120, w: 320, h: 160,
  props: { title: 'Signal Gate', options: ['A', 'B'] },
  data: { choice: 'A' }
});

The catalog below lists each component’s purpose and its props/data model. Use props for configuration and data for state.

Input Controls

OptionPicker
Single-select dropdown with a header and link anchor.
component: "OptionPicker" Category: Input
props
{ title?, label?, options?: string[] }
data
{ choice?: string }
TextInput
Text field with label + header.
component: "TextInput" Category: Input
props
{ title?, label?, placeholder? }
data
{ value?: string }
DateTimeInput
Datetime input (datetime-local).
component: "DateTimeInput" Category: Input
props
{ title?, label?, min?, max?, step?, value? }
data
{ value?: string }
SliderInput
Range slider with live readout.
component: "SliderInput" Category: Input
props
{ title?, label?, min?, max?, step?, unit?, value? }
data
{ value?: number }
MultiChoice
Checkbox list for multi-select.
component: "MultiChoice" Category: Input
props
{ title?, label?, options?: string[] }
data
{ selected?: string[] }

Media & Content

CodeSnippet
Syntax-highlighted code block (auto-detects language).
component: "CodeSnippet" Category: Media
props
{ title?, label?, filename?, language?, code? }
data
{ code?: string, language?: string }
ImageViewer
Image panel with placeholder if no source.
component: "ImageViewer" Category: Media
props
{ title?, label?, src?, alt? }
data
{ src?: string }
VideoPlayer
Video player with optional poster and playback controls.
component: "VideoPlayer" Category: Media
props
{ title?, label?, src?, poster?, controls?, loop?, autoplay?, muted? }
data
{ src?: string }
AudioPlayer
Audio player with playback controls.
component: "AudioPlayer" Category: Media
props
{ title?, label?, src?, controls?, loop?, autoplay?, muted? }
data
{ src?: string }

Charts & Visualizations

LineChart
Multi-series line chart with optional points and legend.
component: "LineChart" Category: Charts
props
{ title?, labels?, legend?, showPoints?, palette?, minX?, maxX?, minY?, maxY? }
data
{ series?: { name, values? | points?, color? }[], values?, points?, labels?, minX?, maxX?, minY?, maxY? }
AreaChart
Stacked-looking area chart with axes/legend options.
component: "AreaChart" Category: Charts
props
{ title?, labels?, legend?, palette?, minX?, maxX?, minY?, maxY? }
data
{ series?: { name, values? | points?, color? }[], values?, points?, labels?, minX?, maxX?, minY?, maxY? }
BarChart
Grouped or stacked bar chart.
component: "BarChart" Category: Charts
props
{ title?, labels?, legend?, palette?, stacked? }
data
{ series?: { name, values?, color? }[], labels?, stacked? }
ScatterChart
Scatter plot with optional legend and axes bounds.
component: "ScatterChart" Category: Charts
props
{ title?, legend?, palette?, minX?, maxX?, minY?, maxY? }
data
{ series?: { name, points: { x, y }[], color? }[], minX?, maxX?, minY?, maxY? }
HeatmapChart
Heatmap matrix with x/y labels.
component: "HeatmapChart" Category: Charts
props
{ title?, xLabels?, yLabels?, palette? }
data
{ matrix?: number[][], xLabels?, yLabels? }
RadarChart
Radar/spider chart with axes labels.
component: "RadarChart" Category: Charts
props
{ title?, axes?, legend?, palette?, max? }
data
{ series?: { name, values: number[], color? }[], axes?, max? }
SparklineChart
Compact single-series trend line.
component: "SparklineChart" Category: Charts
props
{ title?, label?, palette? }
data
{ values?: number[] }

Monitoring & Analytics Panels

FeatureDriftMonitor
Drift thresholds and ranked feature list.
component: "FeatureDriftMonitor" Category: Monitoring
props
{ title?, threshold?, window?, baseline?, features? }
data
{ threshold?, window?, baseline?, features?: { name, psi?, ks?, drift? }[], selected? }
DatasetProfiler
Column list with mini histogram and stats.
component: "DatasetProfiler" Category: Monitoring
props
{ title?, rows?, columns? }
data
{ rows?, columns?: { name, type, missing?, distinct?, stats?, histogram? }[], selected? }
AudioForensics
Waveform + spectrogram with segment list.
component: "AudioForensics" Category: Monitoring
props
{ title?, duration?, sampleRate?, waveform?, spectrogram?, segments? }
data
{ duration?, sampleRate?, waveform?: number[], spectrogram?: number[][], segments?: { start, end, label }[], selectedSegment? }
MediaBatchInspector
Thumbnail grid with anomaly scoring.
component: "MediaBatchInspector" Category: Monitoring
props
{ title?, items? }
data
{ items?: { id, src?, label?, score? }[], selected? }

Graph & Flow Visualizers

CodeDependencyExplorer
Module dependency graph with hot-module list.
component: "CodeDependencyExplorer" Category: Flow
props
{ title?, graph? }
data
{ graph?: { nodes: { id, name, loc?, changed? }[], edges: { from, to }[] } }
DataLineageGraph
Data asset lineage by stage.
component: "DataLineageGraph" Category: Flow
props
{ title?, lineage? }
data
{ lineage?: { nodes: { id, name, stage?, freshness? }[], edges: { from, to }[] } }
PipelineOrchestrator
Stage-based task pipeline with status badges.
component: "PipelineOrchestrator" Category: Flow
props
{ title?, tasks? }
data
{ tasks?: { id, name, status, stage?, duration? }[] }

DevOps & Code Intelligence

GitLogHistory
Paginated commit list with per-file diff summary and compare A/B.
component: "GitLogHistory" Category: DevOps
props
{ title?, pageSize?, maxFiles? }
data
{ commits?: Commit[], filters?: { query?, author?, branch? }, compare?: { a?, b? }, selected?, visibleCount? }
Commit
{ id?, hash?, message?, author?, timestamp?, branch?, tags?, stats?, files? }
stats / files
stats: { files?, additions?, deletions? } | files: { name, additions?, deletions?, status? }[]

Motion Visualization

ParticleTimeseries
Particle-based timeseries visualization with anomaly pulses.
component: "ParticleTimeseries" Category: Motion
props
{ title?, window?, mode?, series?, metrics?, anomalies?, particleCount?, turbulence?, smoothing? }
data
{ series?: { t, v }[], metrics?: { avg?, max?, min?, drift? }, anomalies?: { t }[], mode?, window?, particleCount?, turbulence?, smoothing? }

Operational Intelligence

EventCorrelationCloud
Particle-based correlation map showing related event clusters.
component: "EventCorrelationCloud" Category: Operational Intelligence
props
{ title?, window?, clusters?, links? }
data
{ window?, mode?, eventCount?, particleCount?, turbulence?, clusters?, links? }
clusters
{ id, label?, load?, risk?, color? }[]
links
{ from, to, strength? }[]
ThroughputStream
Animated stream lanes showing stage throughput and saturation.
component: "ThroughputStream" Category: Operational Intelligence
props
{ title?, window?, stages? }
data
{ window?, mode?, particleCount?, turbulence?, stages? }
stages
{ id, label?, throughput?, capacity?, latency?, errorRate?, color? }[]

Conversation & Community

ConversationTimeline
Chronological comment stream with sentiment markers across sources (HN, Reddit, X, YouTube).
component: "ConversationTimeline" Category: Conversation & Community
props
{ title?, window?, items?, groupByThread? }
data
{ window?, items?, selected?, filters?, groupByThread? }
filters
{ sources?: string[] }
items
{ id, source?, author?, sentiment?, score?, timestamp?, text?, threadId?, parentId?, status? }[]