@polygonschmied/stellar-nodes
v0.2.5
Published
Stellar DS node-editor — controlled React Flow canvas with the Stellar skin (Paper-body nodes, Clay handles, inline controls).
Maintainers
Readme
@polygonschmied/stellar-nodes
A controlled node-graph editor on React Flow wrapped in the Stellar skin for Stellar DS — Paper-body nodes, Clay handles, Theater connection wires, and controlled inline controls so the canvas works as a steering surface.
React owns the graph: nodes and edges are props, every canvas interaction (select, move, connect, delete, rename) flows back out through change streams and semantic callbacks.
Install
pnpm add @polygonschmied/stellar-nodes @polygonschmied/stellar-tokensPeer dependencies: react >= 18, react-dom >= 18. @xyflow/react ships as
a direct dependency, and the package imports @xyflow/react/dist/base.css
for you — your build needs a bundler that handles CSS imports (Vite, Next.js,
etc.; the same constraint @polygonschmied/stellar-core already imposes).
Quick start
import "@polygonschmied/stellar-tokens/styles.css";
import {
NodeEditor,
StellarNodeShell,
NodeSlider,
useNodesState,
useEdgesState,
addEdge,
type NodeTypes,
type StellarNodeProps,
} from "@polygonschmied/stellar-nodes";
function AgentNode(props: StellarNodeProps) {
return (
<StellarNodeShell
id={props.id}
selected={props.selected}
label={props.data.label}
subtitle={props.data.subtitle}
badge={props.data.badge}
state={props.data.state}
inputs={[{ id: "in", label: "in" }]}
outputs={[{ id: "handoff", label: "handoff" }]}
>
{/* controlled inline controls — see below */}
</StellarNodeShell>
);
}
const nodeTypes: NodeTypes = { agent: AgentNode };
export function TeamGraph() {
const [nodes, , onNodesChange] = useNodesState([
{ id: "a", type: "agent", position: { x: 0, y: 0 }, data: { label: "Orchestrator" } },
{ id: "b", type: "agent", position: { x: 320, y: 0 }, data: { label: "Researcher" } },
]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
return (
<div style={{ width: 800, height: 500 }}>
<NodeEditor
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
nodeTypes={nodeTypes}
onConnectionCreated={(conn) => setEdges((e) => addEdge(conn, e))}
fitView
/>
</div>
);
}Node types
Register node renderers via nodeTypes and compose them from
StellarNodeShell: header (icon, title + subtitle, badge, accent rail), body
slot, footer slot, and labeled pins (inputs render left target handles,
outputs right source handles).
Appearance rules:
- Per type (icon, accent, which controls render) lives in the node-type component.
- Per instance/state (
label,subtitle,badge,state) lives innode.dataand stays serializable — no callbacks in data. statemaps to modifiers:entry(accent rail),disabled(muted, disable your controls too),error(crimson rail + header tint).
Inline controls
NodeSelect, NodeCheckbox, NodeSlider, NodeTextInput are compact,
strictly controlled form elements for node bodies (all nodrag, the slider
also nowheel). Close over an updater so React stays the state owner:
<NodeSlider
label="Budget (k tokens)"
value={data.budget}
min={0}
max={500}
step={25}
showValue
onChange={(budget) => patchNodeData(props.id, { budget })}
/>Inline rename
Pass onLabelChange to enable double-click rename on node titles. Enter or
blur commits, Escape cancels; the commit arrives as (nodeId, label) and you
write it into node.data.label — the shell only holds the transient draft.
Events
| Prop | Fires when |
| --------------------- | --------------------------------------------- |
| onNodesChange | Any node change (move/select/remove stream) |
| onEdgesChange | Any edge change |
| onNodeSelect | Selection changed ([] on deselect) |
| onNodeDoubleClick | Node double-clicked (title dblclick renames) |
| onNodeMoved | Drag ended (final position on the node) |
| onConnectionCreated | User drew a connection (accept via addEdge) |
| onConnectionRemoved | Edges removed |
| onNodeRemoved | Nodes removed |
| onLabelChange | Inline rename committed |
Helpers you'd otherwise import from @xyflow/react are re-exported:
applyNodeChanges, applyEdgeChanges, addEdge, useNodesState,
useEdgesState, Position, plus the Node/Edge/NodeChange/… types.
Testing
The editor runs in jsdom. Shim ResizeObserver (callback-invoking, with
contentRect), DOMMatrixReadOnly, offsetWidth/offsetHeight and
SVGElement.getBBox — see vitest.setup.ts in this package for a working
reference. Nodes carry data-testid="stl-node-<id>".
License
MIT — see the repo README for the wider picture.
