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.
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).
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
Component contract
Render functions receive { node, api }. The render result can be an element, a lifecycle object, or an HTML string.
- 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.
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).
Events
The view is an event emitter. Components commonly emit node:input.
view.on('node:input', (payload) => {
console.log('component input', payload);
});
- 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)
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 });
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.
view.registerComponent('lazy-widget', {
loader: () => import('./lazy-widget.js')
});
// or per-node:
view.updateNode(id, { loader: () => import('./lazy-widget.js') });
Capturing user input
Write state to the node and emit a typed signal.
api.setData({ selected: value });
api.emit('node:input', { id: node.id, value });
Bulk updates
For large changes, update nodes/edges in bulk from the host app.
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.
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));
}
};
}
});
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.
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
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
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
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
data: {
axes: ['Core', 'Flux', 'Drift', 'Pulse'],
series: [
{ name: 'Alpha', values: [6, 8, 5, 7] },
{ name: 'Beta', values: [4, 6, 4, 5] }
],
max: 10
}
Sparkline
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.
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.
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
{ title?, label?, options?: string[] }{ choice?: string }
TextInput
Text field with label + header.
component: "TextInput"
Category: Input
{ title?, label?, placeholder? }{ value?: string }
DateTimeInput
Datetime input (datetime-local).
component: "DateTimeInput"
Category: Input
{ title?, label?, min?, max?, step?, value? }{ value?: string }
SliderInput
Range slider with live readout.
component: "SliderInput"
Category: Input
{ title?, label?, min?, max?, step?, unit?, value? }{ value?: number }
MultiChoice
Checkbox list for multi-select.
component: "MultiChoice"
Category: Input
{ title?, label?, options?: string[] }{ selected?: string[] }Media & Content
CodeSnippet
Syntax-highlighted code block (auto-detects language).
component: "CodeSnippet"
Category: Media
{ title?, label?, filename?, language?, code? }{ code?: string, language?: string }
ImageViewer
Image panel with placeholder if no source.
component: "ImageViewer"
Category: Media
{ title?, label?, src?, alt? }{ src?: string }
VideoPlayer
Video player with optional poster and playback controls.
component: "VideoPlayer"
Category: Media
{ title?, label?, src?, poster?, controls?, loop?, autoplay?, muted? }{ src?: string }
AudioPlayer
Audio player with playback controls.
component: "AudioPlayer"
Category: Media
{ title?, label?, src?, controls?, loop?, autoplay?, muted? }{ src?: string }Charts & Visualizations
LineChart
Multi-series line chart with optional points and legend.
component: "LineChart"
Category: Charts
{ title?, labels?, legend?, showPoints?, palette?, minX?, maxX?, minY?, maxY? }{ 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
{ title?, labels?, legend?, palette?, minX?, maxX?, minY?, maxY? }{ series?: { name, values? | points?, color? }[], values?, points?, labels?, minX?, maxX?, minY?, maxY? }
BarChart
Grouped or stacked bar chart.
component: "BarChart"
Category: Charts
{ title?, labels?, legend?, palette?, stacked? }{ series?: { name, values?, color? }[], labels?, stacked? }
ScatterChart
Scatter plot with optional legend and axes bounds.
component: "ScatterChart"
Category: Charts
{ title?, legend?, palette?, minX?, maxX?, minY?, maxY? }{ series?: { name, points: { x, y }[], color? }[], minX?, maxX?, minY?, maxY? }
HeatmapChart
Heatmap matrix with x/y labels.
component: "HeatmapChart"
Category: Charts
{ title?, xLabels?, yLabels?, palette? }{ matrix?: number[][], xLabels?, yLabels? }
RadarChart
Radar/spider chart with axes labels.
component: "RadarChart"
Category: Charts
{ title?, axes?, legend?, palette?, max? }{ series?: { name, values: number[], color? }[], axes?, max? }
SparklineChart
Compact single-series trend line.
component: "SparklineChart"
Category: Charts
{ title?, label?, palette? }{ values?: number[] }Monitoring & Analytics Panels
FeatureDriftMonitor
Drift thresholds and ranked feature list.
component: "FeatureDriftMonitor"
Category: Monitoring
{ title?, threshold?, window?, baseline?, features? }{ threshold?, window?, baseline?, features?: { name, psi?, ks?, drift? }[], selected? }
DatasetProfiler
Column list with mini histogram and stats.
component: "DatasetProfiler"
Category: Monitoring
{ title?, rows?, columns? }{ rows?, columns?: { name, type, missing?, distinct?, stats?, histogram? }[], selected? }
AudioForensics
Waveform + spectrogram with segment list.
component: "AudioForensics"
Category: Monitoring
{ title?, duration?, sampleRate?, waveform?, spectrogram?, segments? }{ duration?, sampleRate?, waveform?: number[], spectrogram?: number[][], segments?: { start, end, label }[], selectedSegment? }
MediaBatchInspector
Thumbnail grid with anomaly scoring.
component: "MediaBatchInspector"
Category: Monitoring
{ title?, items? }{ items?: { id, src?, label?, score? }[], selected? }Graph & Flow Visualizers
CodeDependencyExplorer
Module dependency graph with hot-module list.
component: "CodeDependencyExplorer"
Category: Flow
{ title?, graph? }{ graph?: { nodes: { id, name, loc?, changed? }[], edges: { from, to }[] } }
DataLineageGraph
Data asset lineage by stage.
component: "DataLineageGraph"
Category: Flow
{ title?, lineage? }{ lineage?: { nodes: { id, name, stage?, freshness? }[], edges: { from, to }[] } }
PipelineOrchestrator
Stage-based task pipeline with status badges.
component: "PipelineOrchestrator"
Category: Flow
{ title?, tasks? }{ 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
{ title?, pageSize?, maxFiles? }{ commits?: Commit[], filters?: { query?, author?, branch? }, compare?: { a?, b? }, selected?, visibleCount? }{ id?, hash?, message?, author?, timestamp?, branch?, tags?, 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
{ title?, window?, mode?, series?, metrics?, anomalies?, particleCount?, turbulence?, smoothing? }{ 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
{ title?, window?, clusters?, links? }{ window?, mode?, eventCount?, particleCount?, turbulence?, clusters?, links? }{ id, label?, load?, risk?, color? }[]{ from, to, strength? }[]
ThroughputStream
Animated stream lanes showing stage throughput and saturation.
component: "ThroughputStream"
Category: Operational Intelligence
{ title?, window?, stages? }{ window?, mode?, particleCount?, turbulence?, 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
{ title?, window?, items?, groupByThread? }{ window?, items?, selected?, filters?, groupByThread? }{ sources?: string[] }{ id, source?, author?, sentiment?, score?, timestamp?, text?, threadId?, parentId?, status? }[]