Edge
Animated and temporary edges for the workflow canvas.
import { Canvas } from "@/components/ai/canvas";import { Edge } from "@/components/ai/edge";import { Node, NodeHeader, NodeTitle } from "@/components/ai/node";
const nodes = [ { id: "a", type: "step", position: { x: 0, y: 40 }, data: { l: "Start", t: false }, }, { id: "b", type: "step", position: { x: 320, y: 0 }, data: { l: "Check drift", t: true }, }, { id: "c", type: "step", position: { x: 320, y: 120 }, data: { l: "Draft post", t: true }, }, { id: "d", type: "step", position: { x: 320, y: 240 }, data: { l: "Escalate", t: true }, },];
const edges = [ { id: "e1", source: "a", target: "b", type: "animated" }, { id: "e2", source: "a", target: "c" }, { id: "e3", source: "a", target: "d", type: "temporary" },];
const nodeTypes = { step: ({ data }: { data: { l: string; t: boolean } }) => ( <Node handles={{ target: data.t, source: !data.t }} className="w-40"> <NodeHeader className="border-b-0"> <NodeTitle>{data.l}</NodeTitle> </NodeHeader> </Node> ),};
export default function Demo() { return ( <div className="h-72 w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border"> <Canvas edges={edges} edgeTypes={{ animated: Edge.Animated, temporary: Edge.Temporary }} nodes={nodes} nodeTypes={nodeTypes} /> </div> );}<script setup lang="ts">import { markRaw } from "vue";import { Canvas } from "@/components/ai/canvas";import { Edge } from "@/components/ai/edge";import { Node, NodeHeader, NodeTitle } from "@/components/ai/node";
const nodes = [ { id: "a", type: "step", position: { x: 0, y: 40 }, data: { l: "Start", t: false } }, { id: "b", type: "step", position: { x: 320, y: 0 }, data: { l: "Check drift", t: true } }, { id: "c", type: "step", position: { x: 320, y: 120 }, data: { l: "Draft post", t: true } }, { id: "d", type: "step", position: { x: 320, y: 240 }, data: { l: "Escalate", t: true } },];
const edges = [ { id: "e1", source: "a", target: "b", type: "animated" }, { id: "e2", source: "a", target: "c" }, { id: "e3", source: "a", target: "d", type: "temporary" },];
const edgeTypes = { animated: markRaw(Edge.Animated), temporary: markRaw(Edge.Temporary) };</script>
<template> <div class="h-72 w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border"> <Canvas :nodes="nodes" :edges="edges" :edge-types="edgeTypes"> <template #node-step="{ data }"> <Node :handles="{ target: data.t, source: !data.t }" class="w-40"> <NodeHeader class="border-b-0"> <NodeTitle>{{ data.l }}</NodeTitle> </NodeHeader> </Node> </template> </Canvas> </div></template><script lang="ts"> import { Canvas } from "$lib/components/ai/canvas"; import { Edge } from "$lib/components/ai/edge"; import Step from "./_shared/ai-workflow-step.svelte";
const nodeTypes = { step: Step }; const edgeTypes = { animated: Edge.Animated, temporary: Edge.Temporary };
const step = (title: string, target: boolean) => ({ title, compact: true, handles: { target, source: !target } });
let nodes = $state.raw([ { id: "a", type: "step", position: { x: 0, y: 40 }, data: step("Start", false) }, { id: "b", type: "step", position: { x: 320, y: 0 }, data: step("Check drift", true) }, { id: "c", type: "step", position: { x: 320, y: 120 }, data: step("Draft post", true) }, { id: "d", type: "step", position: { x: 320, y: 240 }, data: step("Escalate", true) }, ]); let edges = $state.raw([ { id: "e1", source: "a", target: "b", type: "animated" }, { id: "e2", source: "a", target: "c" }, { id: "e3", source: "a", target: "d", type: "temporary" }, ]);</script>
<div style="height: 288px" class="w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border"> <Canvas bind:nodes bind:edges {nodeTypes} {edgeTypes} /></div>Register as edgeTypes={{ animated: Edge.Animated, temporary: Edge.Temporary }}. Edges without a type use the default solid bezier (--muted-foreground).
Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/ai-edgepnpm dlx shadcn@latest add @edmi-ui/ai-edgeyarn dlx shadcn@latest add @edmi-ui/ai-edgebunx shadcn@latest add @edmi-ui/ai-edgePick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @xyflow/reactTerminal window pnpm add @xyflow/reactTerminal window yarn add @xyflow/reactTerminal window bun add @xyflow/reactCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/ai-edgepnpm dlx shadcn-vue@latest add @edmi-ui/ai-edgeyarn dlx shadcn-vue@latest add @edmi-ui/ai-edgebunx shadcn-vue@latest add @edmi-ui/ai-edgePick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @vue-flow/coreTerminal window pnpm add @vue-flow/coreTerminal window yarn add @vue-flow/coreTerminal window bun add @vue-flow/coreCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-edge.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-edge.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-edge.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-edge.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Copy the code from Component source into your project and update the import paths to match your aliases.
import { Canvas } from "@/components/ai/canvas";import { Edge } from "@/components/ai/edge";
<div className="h-72 w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border"> <Canvas edgeTypes={{ animated: Edge.Animated, temporary: Edge.Temporary }} /></div><script setup lang="ts">import { Canvas } from "@/components/ai/canvas";import { Node, NodeHeader, NodeTitle } from "@/components/ai/node";</script>
<template> <div class="h-72 w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border"> <Canvas> <template #node-step="{ data }"> <Node :handles="{ target: data.t, source: !data.t }" class="w-40"> <NodeHeader class="border-b-0"> <NodeTitle>{{ data.l }}</NodeTitle> </NodeHeader> </Node> </template> </Canvas> </div></template><script lang="ts"> import { Canvas } from "$lib/components/ai/canvas";</script>
<div style="height: 288px" class="w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border"> <Canvas bind:nodes bind:edges /></div>Component source
Section titled “Component source”import type { EdgeProps } from "@xyflow/react";import { BaseEdge, getBezierPath } from "@xyflow/react";
/** Conditional or error path: dotted, muted. */const Temporary = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition,}: EdgeProps) => { const [edgePath] = getBezierPath({ sourcePosition, sourceX, sourceY, targetPosition, targetX, targetY, });
return ( <BaseEdge id={id} path={edgePath} style={{ stroke: "var(--muted-foreground-2)", strokeDasharray: "2 5", strokeLinecap: "round", }} /> );};
/** Active data flow: brand dashes that move toward the target. */const Animated = ({ id, markerEnd, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style,}: EdgeProps) => { const [edgePath] = getBezierPath({ sourcePosition, sourceX, sourceY, targetPosition, targetX, targetY, });
return ( <BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={{ stroke: "var(--brand)", strokeDasharray: "6 5", strokeLinecap: "round", animation: "dashdraw 0.6s linear infinite", ...style, }} /> );};
export const Edge = { Animated, Temporary,};<script setup lang="ts">import type { EdgeProps } from "@vue-flow/core"import { BaseEdge, getBezierPath } from "@vue-flow/core"import { computed } from "vue"
/** Active data flow: brand dashes that move toward the target. */const props = defineProps<EdgeProps>()
const path = computed(() => getBezierPath({ sourceX: props.sourceX, sourceY: props.sourceY, sourcePosition: props.sourcePosition, targetX: props.targetX, targetY: props.targetY, targetPosition: props.targetPosition,})[0])</script>
<template> <BaseEdge :id="props.id" :marker-end="props.markerEnd" :path="path" :style="{ stroke: 'var(--brand)', strokeDasharray: '6 5', strokeLinecap: 'round', animation: 'dashdraw 0.6s linear infinite', ...(props.style as Record<string, string>), }" /></template><script setup lang="ts">import type { EdgeProps } from "@vue-flow/core"import { BaseEdge, getBezierPath } from "@vue-flow/core"import { computed } from "vue"
/** Conditional or error path: dotted, muted. */const props = defineProps<EdgeProps>()
const path = computed(() => getBezierPath({ sourceX: props.sourceX, sourceY: props.sourceY, sourcePosition: props.sourcePosition, targetX: props.targetX, targetY: props.targetY, targetPosition: props.targetPosition,})[0])</script>
<template> <BaseEdge :id="props.id" :path="path" :style="{ stroke: 'var(--muted-foreground-2)', strokeDasharray: '2 5', strokeLinecap: 'round', }" /></template>import Animated from "./Animated.vue";import Temporary from "./Temporary.vue";
export { Animated, Temporary };
/** Same shape as the React port: `edgeTypes = { animated: Edge.Animated, temporary: Edge.Temporary }` (wrap in `markRaw`). */export const Edge = { Animated, Temporary };<script lang="ts"> import { BaseEdge, type EdgeProps, getBezierPath } from "@xyflow/svelte";
/** Active data flow: brand dashes that move toward the target. */ let { id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, markerEnd, }: EdgeProps = $props();
const path = $derived( getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition })[0] );</script>
<BaseEdge {id} {path} {markerEnd} style="stroke: var(--brand); stroke-dasharray: 6 5; stroke-linecap: round; animation: dashdraw 0.6s linear infinite;"/><script lang="ts"> import { BaseEdge, type EdgeProps, getBezierPath } from "@xyflow/svelte";
/** Conditional or error path: dotted, muted. */ let { id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, }: EdgeProps = $props();
const path = $derived( getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition })[0] );</script>
<BaseEdge {id} {path} style="stroke: var(--muted-foreground-2); stroke-dasharray: 2 5; stroke-linecap: round;"/>import Animated from "./animated.svelte";import Temporary from "./temporary.svelte";
export { Animated, Temporary };
/** Same shape as the React port: `edgeTypes = { animated: Edge.Animated, temporary: Edge.Temporary }`. */export const Edge = { Animated, Temporary };| Part | Prop | Type | Default |
|---|---|---|---|
| Edge.Animated | edge type | brand dashes that move to the target | |
| Edge.Temporary | edge type | dotted muted path for conditional or error flow |