Migrating to Vue Flow 2.0
Vue Flow 2.0 rebuilds the core on top of @xyflow/system — the
framework-agnostic engine that also powers React Flow and Svelte Flow. The payoff is a smaller, faster,
and far more consistent API: the same node/edge model, the same accessor names, and the same store shape
across the three libraries.
That alignment means breaking changes. This guide walks through them, grouped by theme, with before/after for each. If you just want the lookup tables, jump to the cheat sheet.
Most apps are touched by §1 (one package), §2 (v-model), and §3 (immutability) — start there.
1. One package: @xyflow/vue
Vue Flow 2.0 is published under a new name — @xyflow/vue — and that single package ships every
built-in: Background, Controls, MiniMap, NodeToolbar, NodeResizer/NodeResizeControl. The
@vue-flow/core package and the separate @vue-flow/node-resizer, @vue-flow/node-toolbar,
@vue-flow/background, @vue-flow/controls, and @vue-flow/minimap add-ons are all replaced by it.
// before
import { VueFlow } from '@vue-flow/core';
import { NodeResizer } from '@vue-flow/node-resizer';
import { NodeToolbar } from '@vue-flow/node-toolbar';
import '@vue-flow/core/dist/style.css';
import '@vue-flow/node-resizer/dist/style.css';
// after — everything from one package
import { NodeResizer, NodeToolbar, VueFlow } from '@xyflow/vue';
import '@xyflow/vue/style.css'; // ships the resize-control styles tooUninstall the @vue-flow/* packages and install @xyflow/vue, then bump the peers: Vue Flow 2.0
requires Vue >=3.5 (it uses useId()) and @vueuse/core v14.
2. Binding nodes & edges
The combined v-model="elements" (and the whole mixed-elements API) is removed. Nodes and edges are
separate.
<!-- before -->
<VueFlow v-model="elements" />
<!-- after — separate, two-way bindings -->
<VueFlow v-model:nodes="nodes" v-model:edges="edges" />
<!-- or one-way -->
<VueFlow :nodes="nodes" :edges="edges" />The element-bucket actions are gone too — use the node/edge equivalents:
| Removed | Use |
|---|---|
setElements(...) | setNodes(...) + setEdges(...) |
addSelectedElements(...) | addSelectedNodes(...) / addSelectedEdges(...) |
getElements / getSelectedElements | getNodes / getEdges / getSelectedNodes / getSelectedEdges |
The binding decides how changes are handled
The applyDefault prop is removed — the binding you choose now determines this:
v-model:nodes/v-model:edges(or no binding at all, when driving the flow throughuseVueFlow()/setupVueFlow()) is managed: Vue Flow applies changes (drag, select, remove) to its store and mirrors them back to your ref.- A bare one-way
:nodes/:edgesis controlled: changes are delivered via@nodes-change/@edges-changeand nothing is applied until you handle them. What those events hand you changed in 2.0, see Changes are a changeset, not an array.
<!-- managed -->
<VueFlow v-model:nodes="nodes" v-model:edges="edges" />
<!-- controlled -->
<VueFlow
:nodes="nodes"
:edges="edges"
@nodes-change="(changes) => (nodes = changes.applyTo(nodes))"
@edges-change="(changes) => (edges = changes.applyTo(edges))"
/>3. Changes are a changeset, not an array
@nodes-change / @edges-change — and the onNodesChange / onEdgesChange hooks — used to hand you a
NodeChange[] / EdgeChange[], which you applied with the standalone applyNodeChanges /
applyEdgeChanges helpers. They now hand you a changeset: a
NodeChangeset or
EdgeChangeset that carries the changes queued for that update and
knows how to apply them.
This only concerns you if you run a controlled flow (a one-way :nodes / :edges binding) or listen
to the change events yourself. With v-model Vue Flow applies the changeset for you.
Applying changes
Call applyTo with your current array; it returns the next one.
// before
import { applyEdgeChanges, applyNodeChanges } from '@xyflow/vue';
function onNodesChange(changes: NodeChange[]) {
nodes.value = applyNodeChanges(changes, nodes.value);
}
function onEdgesChange(changes: EdgeChange[]) {
edges.value = applyEdgeChanges(changes, edges.value);
}// now
import type { Edge, EdgeChangeset, Node, NodeChangeset } from '@xyflow/vue';
function onNodesChange(changes: NodeChangeset<Node>) {
nodes.value = changes.applyTo(nodes.value);
}
function onEdgesChange(changes: EdgeChangeset<Edge>) {
edges.value = changes.applyTo(edges.value);
}Reading changes
A changeset is iterable, and it can hand you changes by type or by element — so you no longer scan an array to find out what happened.
// before
if (changes.some((change) => change.type === 'remove')) {
saveUndoSnapshot();
}
// now
if (changes.getByType('remove').length > 0) {
saveUndoSnapshot();
}for (const change of changes) {
console.log(change.type, change.id);
}
changes.toArray(); // every queued change, as a flat array
changes.getForNode('node-1'); // everything queued for one node (getForEdge on an EdgeChangeset)
changes.size; // how many elements the changeset touchesEditing a changeset before applying it
Because it’s a real object rather than an array you rebuild, you can drop or add changes before applying — useful for vetoing part of an update.
function onNodesChange(changes: NodeChangeset<Node>) {
// ignore anything that would move or delete the locked node
changes.removeForNode('locked-node');
nodes.value = changes.applyTo(nodes.value);
}add(change) queues a change and remove(change) drops one by reference. You can also build a changeset
from scratch — handy in tests, or in a helper that transforms changes before handing them back:
import { NodeChangeset } from '@xyflow/vue';
const changes = new NodeChangeset([{ id: 'node-1', type: 'select', selected: true }]);
nodes.value = changes.applyTo(nodes.value);What happened to applyNodeChanges / applyEdgeChanges
Both are still exported, but they are deprecated and still array-based, so they cannot take the event
payload directly. Use the changeset’s applyTo, or the instance methods of the same name from
useVueFlow() — those take a changeset and write straight to the store:
const { applyNodeChanges } = useVueFlow();
function onNodesChange(changes: NodeChangeset<Node>) {
applyNodeChanges(changes); // no reassignment needed
}If you have to keep using the deprecated util, hand it changes.toArray().
The individual change shapes were renamed in the same release (NodePositionChange → PositionChange, and
so on) — see Types & change shapes.
4. Nodes and edges are immutable
This is the most important behavioral change. To skip deep-reactifying every node and edge (a big perf win on large graphs), Vue Flow no longer tracks in-place mutations of the objects you pass in.
// ❌ no longer triggers a re-render
const node = getNode('1');
node.position = { x: 10, y: 10 };
node.data.label = 'changed';
node.selected = true;
edge.animated = !edge.animated;Update through the store helpers, or reassign your v-model array immutably:
const { updateNode, updateNodeData, updateEdge, updateEdgeData, setNodes, setEdges } = useVueFlow();
updateNode('1', { position: { x: 10, y: 10 } });
updateNodeData('1', { label: 'changed' });
updateEdge('e1-2', { animated: true });
// or immutable reassignment of the bound array
nodes.value = nodes.value.map((n) => (n.id === '1' ? { ...n, selected: true } : n));Two related changes:
- Node defaults are no longer stamped. A node passed without
datakeepsdata: undefined(was{});selected/draggingstayundefineduntil set. Guard optional reads:node.data?.label. getNodes/getEdges/getSelectedNodes/getSelectedEdgesreturnreadonlyarrays..filter/.mapwork fine; reassign/pushis a type error — change membership viasetNodes/addNodes/etc.
5. The node shape: Node vs InternalNode
Your nodes array (v-model:nodes, store.nodes, getNodes(), getNode(id)) now returns the raw Nodes
you passed in — no store-computed fields. The enriched node (absolute position, z-index, measured size,
handle bounds) is the InternalNode, kept in nodeLookup and reached via new accessors:
const { getInternalNode } = useVueFlow();
const internal = getInternalNode('1'); // InternalNode
internal.internals.positionAbsolute;
internal.measured; // { width, height }
// or as a composable (re-resolves reactively)
import { useInternalNode } from '@xyflow/vue';
const node = useInternalNode('1');Field moves on the node:
| Before (top-level) | After |
|---|---|
node.parentNode | node.parentId |
node.computedPosition | node.internals.positionAbsolute (+ node.internals.z) |
node.dimensions | node.measured |
node.handleBounds | node.internals.handleBounds |
node.isParent | parentLookup.get(node.id)?.size > 0 |
node.label (top-level) | node.data.label |
useNode now returns the user-facing Node (it used to return the enriched InternalNode) — reach
for useInternalNode when you need internals/measured. Its id argument also accepts a ref or getter.
Custom node components are unaffected — they still receive
position, dimensions, etc. through their props. But node event payloads now carry the userNode, not theInternalNode(xyflow parity) —onNodeClick,onNodeDrag*, the minimap node events,nodesInitialized, etc. emit your raw node. If a handler read store-computed fields off the event node, resolve the enriched node by id:getInternalNode(node.id).
6. The edge shape: no more GraphEdge
Edges are stored verbatim as your plain Edge objects — there is no enriched edge representation.
GraphEdgeandisGraphEdgeare removed — useEdge.edge.sourceNode/edge.targetNode/edge.sourceXno longer exist. In a custom edge, resolve nodes withuseInternalNode(() => props.source)and read positions straight offEdgeProps(sourceX,sourceY, …).EdgePropsdropssourceNode/targetNode, exposes handles assourceHandleId/targetHandleId, gainsselectable/deletable, and makestype/dataoptional.useEdge().edgeis now aComputedRef.- The per-edge
templateoption (edge.template) is removed — use edge slots (#edge-<type>) or map an edgetypeto a component via theedgeTypesprop. - Auto-generated edge IDs use the
xy-edge__prefix (wasvueflow__edge-), matching react/svelte — this only affects edges created without an explicitid. Update any CSS/selectors or persisted references that matchedvueflow__edge-. setEdges/addEdgeskeep edges whose source/target node isn’t in the store yet (the edge simply isn’t drawn until both nodes exist, emitting theEDGE_SOURCE_MISSING/EDGE_TARGET_MISSINGdev warning). The order ofsetNodesvssetEdgesno longer matters.
7. The store split: useVueFlow + useVueFlowStore
useVueFlow() is now the curated instance — actions, computed getters, and event hooks. The raw
reactive state moved to useVueFlowStore(), with storeToRefs() as the Pinia-style destructure bridge.
// before — everything off useVueFlow()
const { nodes, transform, nodeLookup, setViewport, onConnect } = useVueFlow();
// after
const { setViewport, onConnect, getNodes } = useVueFlow(); // actions + getters + hooks
const { nodes, transform } = storeToRefs(useVueFlowStore()); // value-type state → refs
const { nodeLookup } = useVueFlowStore(); // reactive-Map lookups (no .value)Inside a component (incl. custom nodes/edges), prefer reading the store directly —
const store = useVueFlowStore()thenstore.transform,store.nodesDraggable, … . Reads inside a computed/render track reactively without.value, and you skip re-projecting the whole state into refs on every instance.
| Before | After |
|---|---|
const { nodes } = useVueFlow() | const { nodes } = storeToRefs(useVueFlowStore()) |
const { transform, dimensions, nodesDraggable } = useVueFlow() | const { transform, dimensions, nodesDraggable } = storeToRefs(useVueFlowStore()) |
const { nodeLookup } = useVueFlow() | const { nodeLookup } = useVueFlowStore() |
const { setViewport, getNodes, onConnect } = useVueFlow() | unchanged |
There is intentionally no useStoreApi — Vue’s reactivity makes it redundant (useVueFlowStore() serves both
reactive reads and current-value reads; subscribe with watch(() => store.x, …)).
The ongoing connection is also consolidated: the separate connectionStartHandle, connectionEndHandle,
connectionPosition, and connectionStatus store fields are replaced by a single connection object
(ConnectionState). Read connection.fromHandle, connection.toHandle, connection.pointer, and
connection.isValid — matching useConnection().
8. No global registry — the provider model
useVueFlow() no longer takes any argument; it’s a pure consumer that returns the store from the nearest
<VueFlow> / <VueFlowProvider> ancestor (and throws if there isn’t one). The global flow registry and the
$vueFlowStorage singleton are gone.
// before
const { ... } = useVueFlow({ nodes, edges, id: 'my-flow' });
const { ... } = useVueFlow('my-flow'); // reach a flow from outside
// after — pass options as props, consume with no args from any descendant
const { ... } = useVueFlow();Multiple flows on a page each get their own <VueFlowProvider> (or <VueFlow>) tree — they no longer share
a registry, so they can’t collide and don’t need distinct ids.
<VueFlowProvider> accepts the same options as setupVueFlow() to seed its store — pass nodes,
edges, or any other VueFlowProps and they become the store’s initial values (read once at creation):
<VueFlowProvider :nodes="initialNodes" :edges="initialEdges">
<Toolbar />
<VueFlow />
</VueFlowProvider>When you own the component that renders <VueFlow>, prefer setupVueFlow(), the composable form that provides (and seeds) the store from the same <script setup> without a wrapper component.
9. API renames (xyflow parity)
findNode(id) → getNode(id)
findEdge(id) → getEdge(id)
project(point) → screenToFlowPosition(point)
screenToFlowCoordinate → screenToFlowPosition
flowToScreenCoordinate → flowToScreenPositionEdge “update” → “reconnect” (xyflow v12 vocabulary):
| Before | After |
|---|---|
updateEdge(oldEdge, connection) | reconnectEdge(oldEdge, connection) |
@edge-update-start / @edge-update / @edge-update-end | @reconnect-start / @reconnect / @reconnect-end |
edgeUpdaterRadius | reconnectRadius |
edges-updatable / edge.updatable | edges-reconnectable / edge.reconnectable |
EdgeUpdatable / EdgeUpdateEvent | EdgeReconnectable / EdgeReconnectEvent |
updateEdgeis reused for a new purpose:updateEdge(id, edgeUpdate, { replace? })— a partial edge update (the edge analogue ofupdateNode). Edge reconnection is nowreconnectEdge.
Instance actions: $reset → reset, and $destroy is removed (it was a no-op — the store is owned
by its provider and torn down when that component unmounts).
isValidConnection now receives only the attempted connection, matching React/Svelte Flow. The
second argument ({ nodes, edges, sourceNode, targetNode }) is removed — read those from useVueFlow()
inside your handler if you need them.
<Handle> props connectableStart / connectableEnd → isConnectableStart / isConnectableEnd.
useConnection returns a ComputedRef<ConnectionState> — { inProgress, isValid, from, fromHandle, fromPosition, fromNode, to, toHandle, toPosition, toNode, pointer } — instead of { startHandle, endHandle, status, position }.
VueFlowStore → VueFlowInstance (the type returned by useVueFlow() / exposed by a <VueFlow> template
ref). The old VueFlowStore name is removed.
10. connectionMode defaults to strict
In strict mode a source handle only connects to a target handle (and vice-versa) — same-type connections
are rejected. To restore the old behavior where every handle acts as a source:
<VueFlow connection-mode="loose" />11. Init / fit props & defaults
| Before | After |
|---|---|
:fit-view-on-init="true" | :fit-view="true" |
| (initial fit was not configurable) | :fit-view-options="{ padding: 0.2, maxZoom: 1 }" |
fitView()now queues — it waits for the nodes to be committed and measured before fitting, so it frames the current graph even right afteraddNodes()or a reposition. You no longer need anextTick()beforefitView(). It returns a promise you can optionallyawait.fitView/fitBoundsdefault padding is now5%.edgesReconnectableandelevateEdgesOnSelectnow default totrue. Edges are reconnectable by default (only in effect once you handle@reconnect) and selected edges are raised above their siblings — set:edges-reconnectable="false"/:elevate-edges-on-select="false"to opt out.nodeOriginis now actually honored (it was previously ignored), and a newnodeClickDistanceprop sets how far the pointer may move and still count as a node click.
Viewport option types now reuse @xyflow/system (renamed; same shape unless noted):
| Before (vue-flow) | After (@xyflow/system) |
|---|---|
FitViewParams | FitViewOptions |
TransitionOptions | ViewportHelperFunctionOptions |
ViewportPositionFunc | Project |
fitView({ nodes })takes node objects, not ids:fitView({ nodes: ['a', 'b'] })→fitView({ nodes: [{ id: 'a' }, { id: 'b' }] }).fitView’soffsetoption is removed — usepadding(a number, a'10px'/'5%'string, or{ top, right, bottom, left }).- New
ease+interpolateoptions on every viewport function, alongsideduration.
12. Deleting elements
removeNodesremoves connected edges by default (removeNodes(nodes, removeConnectedEdges = true, removeChildren = false)). Passfalseto keep the edges.- New
onBeforeDeleteprop — a guard consulted before delete-key /deleteElementsremovals; returnfalseto cancel,trueto proceed, or{ nodes, edges }to delete a subset. - New
deleteElements({ nodes, edges })action — deletes the given elements plus their connected edges and children (gated byonBeforeDelete), resolving to the removed set. - New
nodesDelete/edgesDelete/deleteevents (onNodesDelete/onEdgesDelete/onDelete) fire fromdeleteElementsafteronBeforeDeleteresolves. TheOnDeletehandler type is exported alongsideOnBeforeDelete.
13. Removed deprecated APIs
| Removed | Use |
|---|---|
autoConnect prop / Connector type | @connect (or onConnect) + addEdge/addEdges |
useHandleConnections | useNodeConnections (type→handleType (optional), id→handleId) |
HandleConnection type | NodeConnection |
connectionLineType / connectionLineStyle props | connectionLineOptions.type / .style |
paneReady event | init (@init / onInit) |
FlowExportObject.position / .zoom | FlowExportObject.viewport ({ x, y, zoom }) |
updateNodeInternals() (no-arg form) | updateNodeInternals(id) / updateNodeInternals(ids) |
useZoomPanHelper | useVueFlow() zoom/pan actions (zoomIn, zoomOut, fitView, setViewport) |
useVueFlow().fromObject() / FlowImportObject | restore manually after onInit (set nodes/edges + setViewport); toObject() is unchanged |
Handle new connections with @connect + addEdge (the autoConnect replacement):
<VueFlow @connect="(connection) => (edges = addEdge(connection, edges))" />14. Types & change shapes
NodeProps/EdgePropstake the element type, not the data type:NodeProps<MyData>→NodeProps<Node<MyData, 'myType'>>(same forEdgeProps<Edge<MyData, 'myType'>>).GraphNodeis renamed toInternalNode(and theisGraphNodeguard toisInternalNode).HandleElement→HandleandEdgePositions→EdgePosition; the deprecatedProjecttype is no longer re-exported, andOnConnectStartParamsfields are now nullable.useVueFlow<NodeType, EdgeType>()is fully typed on both generics (xyflow order).- Padding uses
@xyflow/system’sPaddingtype for bothfitView/fitBoundsand a nodeextent’s padding. A plain number still works; you can also pass a'10px'/'5%'string or a per-side object. The old positional-tuple extent padding is removed:padding: [10, 20]→padding: { y: 10, x: 20 }. - Custom connection lines use
from*/to*props.ConnectionLinePropsmoved fromsource*/target*to xyflow’sfrom*/to*(sourceX→fromX,targetNode→toNode, …).markerStart/markerEndare now optional. - An edge’s
datadefaults toRecord<string, unknown>(wasany, via the now-removedElementData). - Changes are delivered as a changeset, not a
NodeChange[]/EdgeChange[]— see its own section. - Change types mirror
@xyflow/system, and the per-element names are gone. The node/edge prefixes were dropped now that the shapes are shared:NodeDimensionChange→DimensionChange,NodePositionChange→PositionChange,NodeSelectionChange/EdgeSelectionChange→SelectionChange,NodeAddChange/EdgeAddChange→AddChange,NodeRemoveChange/EdgeRemoveChange→RemoveChange. (NodeChangeandEdgeChange— the unions you actually receive — keep their names.) Their fields moved too:updateStyle→setAttributes,from→positionAbsolute, an add change’sitemis the userNode/Edge(with an optionalindex), and a remove change is{ id, type: 'remove' }only. PanelPositionType→PanelPosition(@xyflow/system’s type). It now also accepts'center-left'and'center-right'.ConnectionLineType.SimpleBezier’s value changed from'simple-bezier'to'simplebezier'; use the enum rather than the raw string and nothing changes.- The catch-all
objectwas dropped from the edgelabeltype (EdgeProps/EdgeTextProps/BaseEdgeProps) so it matches the actualstring | VNode | Component.
15. Styles & CSS variables
The stylesheets and theme variables now mirror @xyflow/react/@xyflow/svelte.
theme-default.css was removed. style.css is now the full default theme (necessary structure and the
built-in look) — import just that. A new base.css ships the structure with only minimal theming.
// before
import '@vue-flow/core/dist/style.css'; // structure only
import '@vue-flow/core/dist/theme-default.css'; // the default theme
// after — style.css IS the full theme
import '@xyflow/vue/style.css';
// …or theme it yourself on top of the minimal base:
// import '@xyflow/vue/base.css';--vf-* → --xy-*. The theme custom properties are renamed to the shared --xy-* set (identical to
react/svelte). Each rule reads var(--xy-x, var(--xy-x-default)), so override the un-suffixed variable and
Vue Flow falls back to the shipped --xy-x-default. The common ones:
| before | after |
|---|---|
--vf-node-bg | --xy-node-background-color |
--vf-node-text | --xy-node-color |
--vf-handle | --xy-handle-background-color |
--vf-handle-border | --xy-handle-border-color |
--vf-connection-path | --xy-edge-stroke / --xy-connectionline-stroke |
--vf-edge-text | --xy-edge-label-color |
--vf-edge-text-bg | --xy-edge-label-background-color |
--vf-controls-bg | --xy-controls-button-background-color |
--vf-minimap-bg | --xy-minimap-background-color |
Three element classes were renamed to match xyflow. The internal DOM now nests exactly like
@xyflow/react/@xyflow/svelte — renderer (outer pan/zoom container) › pane (drag/selection surface) ›
viewport (the transformed layer):
| before | after |
|---|---|
.vue-flow__transformationpane | .vue-flow__viewport |
.vue-flow__viewport | .vue-flow__renderer |
.vue-flow__edge-labels | .vue-flow__edgelabel-renderer |
Note viewport now refers to the transformed inner layer (it was the outer container before). Everything
keeps the vue-flow__ prefix.
Uniform node accents. The built-in input/output node types no longer have blue/pink accent borders —
every default node type uses the same neutral border. Re-add per-type colors with your own CSS if you want them.
16. selectionKeyCode={true} → selectionOnDrag
Drawing a selection box on a plain drag (no key held) used to be expressed by setting selectionKeyCode to
true. It’s now an explicit boolean prop, selectionOnDrag, matching @xyflow/react:
<!-- before -->
<VueFlow :selection-key-code="true" :pan-on-drag="false" />
<!-- after -->
<VueFlow :selection-on-drag="true" :pan-on-drag="false" />selectionKeyCode goes back to being just the key you hold to select (default 'Shift'). Pair
selectionOnDrag with :pan-on-drag="false" or a non-left button (e.g. :pan-on-drag="[1, 2]") so a
left-drag selects instead of panning.
Cheat sheet
// packages
'@vue-flow/core' | '@vue-flow/node-resizer' | '@vue-flow/node-toolbar' | … → '@xyflow/vue'
import 'theme-default.css' → removed; '@xyflow/vue/style.css' is the full theme (or base.css for minimal)
// binding & changes
<VueFlow v-model="elements"> → <VueFlow v-model:nodes="nodes" v-model:edges="edges">
applyDefault prop → removed; v-model = managed, one-way :nodes/:edges = controlled
@nodes-change payload → NodeChangeset/EdgeChangeset (was NodeChange[]/EdgeChange[])
applyNodeChanges(changes, ns) → changes.applyTo(ns)
changes.some(c => c.type===x) → changes.getByType(x).length > 0
NodePositionChange etc. → PositionChange/DimensionChange/SelectionChange/AddChange/RemoveChange
// store
useVueFlow().nodes / .transform / .nodeLookup → storeToRefs(useVueFlowStore()) / useVueFlowStore()
useVueFlow(id | options) → useVueFlow() inside <VueFlow>/<VueFlowProvider>
connectionStartHandle / … → connection (ConnectionState)
// accessors & helpers
findNode/findEdge → getNode/getEdge
project → screenToFlowPosition
updateEdge(old, conn) → reconnectEdge(old, conn)
@edge-update* → @reconnect*
$reset / $destroy → reset / (removed)
updateNodeInternals() → updateNodeInternals(id) // no-arg form removed
autoConnect prop → @connect + addEdge
// node fields
node.parentNode → node.parentId
node.computedPosition → getInternalNode(id).internals.positionAbsolute
node.dimensions → getInternalNode(id).measured
node.label → node.data.label
useNode(id) → user Node (was InternalNode); useInternalNode(id) for internals
// types
VueFlowStore → VueFlowInstance
GraphEdge / GraphNode → Edge / InternalNode
HandleElement → Handle
EdgePositions → EdgePosition
NodeProps<Data> → NodeProps<Node<Data, 'type'>>
PanelPositionType → PanelPosition // + 'center-left' / 'center-right'
FitViewParams → FitViewOptions
// behavior & defaults
node.x = … → updateNode(id, { x: … }) / immutable reassignment
removeNodes(id) → also removes connected edges (pass `false` to keep them)
connectionMode → defaults to 'strict'
edgesReconnectable / elevateEdgesOnSelect → default to true
fitView() → queues (no nextTick needed); default padding now 5%
:selection-key-code="true" → :selection-on-drag="true"Hitting something this guide doesn’t cover? Open an issue — it likely belongs here.