Skip to Content

Teleport

import type { Node } from '@xyflow/vue'; import { nextTick, shallowRef } from 'vue'; import { getConnectedEdges, useVueFlow } from '@xyflow/vue'; export type TeleportNodeData = { label?: string; /** the selector the node is currently teleported into, or null when it lives in the graph */ destination?: string | null; }; export type TeleportNode = Node<TeleportNodeData, 'teleportable'>; type Animation = 'fade' | 'shrink'; /** * Drives a single teleportable node: it stays a graph node (edges still track its position) but its DOM is * moved into another element via Vue's `<Teleport>`. Animations that resize the node call * `updateNodeInternals` so the handle positions — and therefore the connected edges — stay correct. */ export function useTeleport(id: string) { const animation = shallowRef<Animation>('fade'); const transition = shallowRef(false); const target = shallowRef<string | null>(null); const { updateNodeInternals, updateNodeData, getNode, getEdges, setEdges } = useVueFlow<TeleportNode>(); // hide an edge while either of its endpoints is teleported out of the graph function syncHiddenEdges(connectedEdgeIds: string[]) { setEdges((edges) => edges.map((edge) => connectedEdgeIds.includes(edge.id) ? { ...edge, hidden: !!getNode(edge.source)?.data.destination || !!getNode(edge.target)?.data.destination, } : edge, ), ); } function fade(destination: string | null, onFinish: () => void) { setTimeout(() => { target.value = destination; setTimeout(() => { transition.value = false; // when returning to the graph, defer hiding edges until the node is back if (!destination) { onFinish(); } }, 500); }, 500); } function shrink(destination: string | null, onFinish: () => void) { setTimeout(() => { target.value = destination; setTimeout(() => { transition.value = false; if (!destination) { setTimeout(() => { updateNodeInternals([id]); nextTick(onFinish); }, 500); } }, 500); }, 500); } function onClick(destination: string | null) { const node = getNode(id); if (!node) { return; } transition.value = true; // remember where the node went so `syncHiddenEdges` can check both endpoints updateNodeData(id, { destination }); const connectedEdgeIds = getConnectedEdges([node], [...getEdges.value]).map((edge) => edge.id); // hide edges immediately when leaving the graph if (destination) { syncHiddenEdges(connectedEdgeIds); } const onFinish = () => syncHiddenEdges(connectedEdgeIds); if (animation.value === 'shrink') { shrink(destination, onFinish); } else { fade(destination, onFinish); } } return { animation, transition, target, onClick }; }
Last updated on