diff --git a/package-lock.json b/package-lock.json
index 64eadec..46ca205 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,6 @@
"version": "0.1.0",
"dependencies": {
"@codemirror/lang-javascript": "^6.2.2",
- "@codemirror/lang-yaml": "^6.0.2",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-menubar": "^1.1.16",
"@radix-ui/react-select": "^2.2.6",
@@ -20,7 +19,6 @@
"@xyflow/react": "^12.10.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "js-yaml": "^4.1.0",
"lucide-react": "^0.577.0",
"react": "18.2.0",
"react-dom": "18.2.0",
@@ -29,7 +27,6 @@
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
- "@types/js-yaml": "^4.0.9",
"@types/node": "^25.3.3",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
@@ -589,21 +586,6 @@
"@lezer/javascript": "^1.0.0"
}
},
- "node_modules/@codemirror/lang-yaml": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
- "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
- "license": "MIT",
- "dependencies": {
- "@codemirror/autocomplete": "^6.0.0",
- "@codemirror/language": "^6.0.0",
- "@codemirror/state": "^6.0.0",
- "@lezer/common": "^1.2.0",
- "@lezer/highlight": "^1.2.0",
- "@lezer/lr": "^1.0.0",
- "@lezer/yaml": "^1.0.0"
- }
- },
"node_modules/@codemirror/language": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz",
@@ -1490,17 +1472,6 @@
"@lezer/common": "^1.0.0"
}
},
- "node_modules/@lezer/yaml": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz",
- "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==",
- "license": "MIT",
- "dependencies": {
- "@lezer/common": "^1.2.0",
- "@lezer/highlight": "^1.0.0",
- "@lezer/lr": "^1.4.0"
- }
- },
"node_modules/@marijn/find-cluster-break": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
@@ -3268,13 +3239,6 @@
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"license": "MIT"
},
- "node_modules/@types/js-yaml": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
- "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/node": {
"version": "25.3.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
@@ -3572,6 +3536,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
"license": "Python-2.0"
},
"node_modules/aria-hidden": {
@@ -5628,6 +5593,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
diff --git a/src/App.tsx b/src/App.tsx
index 614a8b6..a6c2599 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -17,6 +17,7 @@ import ConfigNode from './components/graph/ConfigNode'
import FunctionNode from './components/graph/FunctionNode'
import RenderingNode from './components/graph/RenderingNode'
import VariableNode from './components/graph/VariableNode'
+import { AnimatedEdge } from './components/graph/AnimatedEdge'
import FlowContext from './lib/flowContext'
import { useTheme } from './lib/themeContext'
import {
@@ -53,7 +54,9 @@ const initialNodes: Node[] = [
},
].map((n) => ({ ...n, id: n.id ?? genId() }))
-const initialEdges: Edge[] = [{ id: 'e-config-render', source: 'config-1', target: 'render-1' }]
+const initialEdges: Edge[] = [
+ { id: 'e-config-render', source: 'config-1', target: 'render-1', type: 'animated' },
+]
export default function App() {
const { theme, toggleTheme } = useTheme()
@@ -70,6 +73,8 @@ export default function App() {
[]
)
+ const edgeTypes = React.useMemo(() => ({ animated: AnimatedEdge }), [])
+
const onConnect = React.useCallback(
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
[setEdges]
@@ -172,6 +177,8 @@ export default function App() {
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
+ edgeTypes={edgeTypes}
+ defaultEdgeOptions={{ type: 'animated' }}
colorMode={theme as ColorMode}
snapToGrid
snapGrid={SNAP_GRID}
diff --git a/src/components/graph/AnimatedEdge.tsx b/src/components/graph/AnimatedEdge.tsx
new file mode 100644
index 0000000..5b7272b
--- /dev/null
+++ b/src/components/graph/AnimatedEdge.tsx
@@ -0,0 +1,99 @@
+import React from 'react'
+import {
+ BaseEdge,
+ getBezierPath,
+ type EdgeProps,
+} from '@xyflow/react'
+
+const EDGE_STROKE_WIDTH = 2
+const DOT_MARKER_R = 1.5
+
+export function AnimatedEdge({
+ id,
+ sourceX,
+ sourceY,
+ targetX,
+ targetY,
+ sourcePosition,
+ targetPosition,
+ style,
+ label,
+ labelStyle,
+ labelShowBg,
+ labelBgStyle,
+ labelBgPadding,
+ labelBgBorderRadius,
+ labelX,
+ labelY,
+ interactionWidth,
+}: EdgeProps) {
+ const [edgePath] = getBezierPath({
+ sourceX,
+ sourceY,
+ targetX,
+ targetY,
+ sourcePosition,
+ targetPosition,
+ })
+
+ const startId = `animated-edge-dot-start-${id}`
+ const endId = `animated-edge-dot-end-${id}`
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/styles.css b/src/styles.css
index 045f8b9..9ef5f24 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -32,6 +32,25 @@ body {
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.15);
}
+/* Animated React Flow edges: thicker stroke + path animation */
+.react-flow__edge path.animated-edge-path,
+.animated-edge-path {
+ stroke-width: 4;
+ stroke: hsl(var(--foreground) / 0.6);
+ fill: none;
+ stroke-dasharray: 8 6;
+ animation: edge-flow 0.6s linear infinite;
+}
+
+@keyframes edge-flow {
+ from {
+ stroke-dashoffset: 0;
+ }
+ to {
+ stroke-dashoffset: 14;
+ }
+}
+
/* Small helper for monospace pre output */
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Roboto Mono', 'Courier New', monospace;