diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 1c7bda8e7..dfcd8aa4f 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -84,29 +84,66 @@ /* tspan { font-size: 6px !important; } */ + /* .flowchart-link { + stroke-dasharray: 4, 4 !important; + animation: flow 1s linear infinite; + animation: dashdraw 4.93282s linear infinite; + stroke-width: 2px !important; + } */ + + @keyframes dashdraw { + from { + stroke-dashoffset: 0; + } + } + + /*stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: 4.932820s linear infinite;*/ + /* stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: dashdraw 4.932820s linear infinite;*/ -
----
-config:
-  layout: elk
----
+    
       flowchart LR
-      subgraph S2
-      subgraph s1["APA"]
-      D{"Use the editor"}
-      end
-
-
-      D -- Mermaid js --> I{"fa:fa-code Text"}
-            D --> I
-            D --> I
-
-      end
+        A --> B
     
+      flowchart LR
+        A e1@==> B
+        e1@{ animate: true}
+    
+
+flowchart LR
+  A e1@--> B
+  classDef animate stroke-width:2,stroke-dasharray:10\,8,stroke-dashoffset:-180,animation: edge-animation-frame 6s linear infinite, stroke-linecap: round
+  class e1 animate
+    
+

infinite

+
+flowchart LR
+  A e1@--> B
+  classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+    
+

Mermaid - edge-animation-slow

+
+flowchart LR
+  A e1@--> B
+e1@{ animation: fast}
+    
+

Mermaid - edge-animation-fast

+
+flowchart LR
+  A e1@--> B
+  classDef animate stroke-dasharray: 1000,stroke-dashoffset: 1000,animation: dash 10s linear;
+  class e1 edge-animation-fast
+    
+ +
+
+info    
+
 ---
 config:
   layout: elk
@@ -131,7 +168,7 @@ config:
       end
       end
     
-
+    
 ---
 config:
   layout: elk
@@ -144,7 +181,7 @@ config:
       D-->I
       D-->I
     
-
+    
 ---
 config:
   layout: elk
@@ -183,7 +220,7 @@ flowchart LR
     n8@{ shape: rect}
 
     
-
+    
 ---
 config:
   layout: elk
@@ -199,7 +236,7 @@ flowchart LR
 
 
     
-
+    
 ---
 config:
   layout: elk
@@ -208,7 +245,7 @@ flowchart LR
     A{A} --> B & C
 
-
+    
 ---
 config:
   layout: elk
@@ -220,7 +257,7 @@ flowchart LR
     end
 
-
+    
 ---
 config:
   layout: elk
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
index 1dbc789c9..ccb8a8e94 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
@@ -24,7 +24,7 @@ import type {
   FlowLink,
   FlowVertexTypeParam,
 } from './types.js';
-import type { NodeMetaData } from '../../types.js';
+import type { NodeMetaData, EdgeMetaData } from '../../types.js';
 
 const MERMAID_DOM_ID_PREFIX = 'flowchart-';
 let vertexCounter = 0;
@@ -71,12 +71,38 @@ export const addVertex = function (
   classes: string[],
   dir: string,
   props = {},
-  shapeData: any
+  metadata: any
 ) {
-  // console.log('addVertex', id, shapeData);
   if (!id || id.trim().length === 0) {
     return;
   }
+  // Extract the metadata from the shapeData, the syntax for adding metadata for nodes and edges is the same
+  // so at this point we don't know if it's a node or an edge, but we can still extract the metadata
+  let doc;
+  if (metadata !== undefined) {
+    let yamlData;
+    // detect if shapeData contains a newline character
+    if (!metadata.includes('\n')) {
+      yamlData = '{\n' + metadata + '\n}';
+    } else {
+      yamlData = metadata + '\n';
+    }
+    doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
+  }
+
+  // Check if this is an edge
+  const edge = edges.find((e) => e.id === id);
+  if (edge) {
+    const edgeDoc = doc as EdgeMetaData;
+    if (edgeDoc?.animate) {
+      edge.animate = edgeDoc.animate;
+    }
+    if (edgeDoc?.animation) {
+      edge.animation = edgeDoc.animation;
+    }
+    return;
+  }
+
   let txt;
 
   let vertex = vertices.get(id);
@@ -128,19 +154,7 @@ export const addVertex = function (
     Object.assign(vertex.props, props);
   }
 
-  if (shapeData !== undefined) {
-    let yamlData;
-    // detect if shapeData contains a newline character
-    // console.log('shapeData', shapeData);
-    if (!shapeData.includes('\n')) {
-      // console.log('yamlData shapeData has no new lines', shapeData);
-      yamlData = '{\n' + shapeData + '\n}';
-    } else {
-      // console.log('yamlData shapeData has new lines', shapeData);
-      yamlData = shapeData + '\n';
-    }
-    // console.log('yamlData', yamlData);
-    const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
+  if (doc !== undefined) {
     if (doc.shape) {
       if (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_')) {
         throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
@@ -187,11 +201,18 @@ export const addVertex = function (
  * Function called by parser when a link/edge definition has been found
  *
  */
-export const addSingleLink = function (_start: string, _end: string, type: any) {
+export const addSingleLink = function (_start: string, _end: string, type: any, id?: string) {
   const start = _start;
   const end = _end;
 
-  const edge: FlowEdge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
+  const edge: FlowEdge = {
+    start: start,
+    end: end,
+    type: undefined,
+    text: '',
+    labelType: 'text',
+    classes: [],
+  };
   log.info('abc78 Got edge...', edge);
   const linkTextObj = type.text;
 
@@ -210,6 +231,9 @@ export const addSingleLink = function (_start: string, _end: string, type: any)
     edge.stroke = type.stroke;
     edge.length = type.length > 10 ? 10 : type.length;
   }
+  if (id) {
+    edge.id = id;
+  }
 
   if (edges.length < (config.maxEdges ?? 500)) {
     log.info('Pushing edge...');
@@ -225,11 +249,17 @@ You have to call mermaid.initialize.`
   }
 };
 
-export const addLink = function (_start: string[], _end: string[], type: unknown) {
-  log.info('addLink', _start, _end, type);
+export const addLink = function (_start: string[], _end: string[], linkData: unknown) {
+  const id =
+    linkData && typeof linkData === 'object' && 'id' in linkData
+      ? linkData.id?.replace('@', '')
+      : undefined;
+
+  log.info('addLink', _start, _end, id);
+
   for (const start of _start) {
     for (const end of _end) {
-      addSingleLink(start, end, type);
+      addSingleLink(start, end, linkData, id);
     }
   }
 };
@@ -282,7 +312,13 @@ export const updateLink = function (positions: ('default' | number)[], style: st
   });
 };
 
-export const addClass = function (ids: string, style: string[]) {
+export const addClass = function (ids: string, _style: string[]) {
+  const style = _style
+    .join()
+    .replace(/\\,/g, '§§§')
+    .replace(/,/g, ';')
+    .replace(/§§§/g, ',')
+    .split(';');
   ids.split(',').forEach(function (id) {
     let classNode = classes.get(id);
     if (classNode === undefined) {
@@ -337,6 +373,10 @@ export const setClass = function (ids: string, className: string) {
     if (vertex) {
       vertex.classes.push(className);
     }
+    const edge = edges.find((e) => e.id === id);
+    if (edge) {
+      edge.classes.push(className);
+    }
     const subGraph = subGraphLookup.get(id);
     if (subGraph) {
       subGraph.classes.push(className);
@@ -997,7 +1037,7 @@ export const getData = () => {
       styles.push(...rawEdge.style);
     }
     const edge: Edge = {
-      id: getEdgeId(rawEdge.start, rawEdge.end, { counter: index, prefix: 'L' }),
+      id: getEdgeId(rawEdge.start, rawEdge.end, { counter: index, prefix: 'L' }, rawEdge.id),
       start: rawEdge.start,
       end: rawEdge.end,
       type: rawEdge.type ?? 'normal',
@@ -1009,14 +1049,20 @@ export const getData = () => {
         rawEdge?.stroke === 'invisible'
           ? ''
           : 'edge-thickness-normal edge-pattern-solid flowchart-link',
-      arrowTypeStart: rawEdge?.stroke === 'invisible' ? 'none' : arrowTypeStart,
-      arrowTypeEnd: rawEdge?.stroke === 'invisible' ? 'none' : arrowTypeEnd,
+      arrowTypeStart:
+        rawEdge?.stroke === 'invisible' || rawEdge?.type === 'arrow_open' ? 'none' : arrowTypeStart,
+      arrowTypeEnd:
+        rawEdge?.stroke === 'invisible' || rawEdge?.type === 'arrow_open' ? 'none' : arrowTypeEnd,
       arrowheadStyle: 'fill: #333',
+      cssCompiledStyles: getCompiledStyles(rawEdge.classes),
       labelStyle: styles,
       style: styles,
       pattern: rawEdge.stroke,
       look: config.look,
+      animate: rawEdge.animate,
+      animation: rawEdge.animation,
     };
+
     edges.push(edge);
   });
 
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
index 4ae289bad..5682c9bed 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
@@ -39,6 +39,27 @@ const doubleEndedEdges = [
   { edgeStart: '<==', edgeEnd: '==>', stroke: 'thick', type: 'double_arrow_point' },
   { edgeStart: '<-.', edgeEnd: '.->', stroke: 'dotted', type: 'double_arrow_point' },
 ];
+const regularEdges = [
+  { edgeStart: '--', edgeEnd: '--x', stroke: 'normal', type: 'arrow_cross' },
+  { edgeStart: '==', edgeEnd: '==x', stroke: 'thick', type: 'arrow_cross' },
+  { edgeStart: '-.', edgeEnd: '.-x', stroke: 'dotted', type: 'arrow_cross' },
+  { edgeStart: '--', edgeEnd: '--o', stroke: 'normal', type: 'arrow_circle' },
+  { edgeStart: '==', edgeEnd: '==o', stroke: 'thick', type: 'arrow_circle' },
+  { edgeStart: '-.', edgeEnd: '.-o', stroke: 'dotted', type: 'arrow_circle' },
+  { edgeStart: '--', edgeEnd: '-->', stroke: 'normal', type: 'arrow_point' },
+  { edgeStart: '==', edgeEnd: '==>', stroke: 'thick', type: 'arrow_point' },
+  { edgeStart: '-.', edgeEnd: '.->', stroke: 'dotted', type: 'arrow_point' },
+
+  { edgeStart: '--', edgeEnd: '----x', stroke: 'normal', type: 'arrow_cross' },
+  { edgeStart: '==', edgeEnd: '====x', stroke: 'thick', type: 'arrow_cross' },
+  { edgeStart: '-.', edgeEnd: '...-x', stroke: 'dotted', type: 'arrow_cross' },
+  { edgeStart: '--', edgeEnd: '----o', stroke: 'normal', type: 'arrow_circle' },
+  { edgeStart: '==', edgeEnd: '====o', stroke: 'thick', type: 'arrow_circle' },
+  { edgeStart: '-.', edgeEnd: '...-o', stroke: 'dotted', type: 'arrow_circle' },
+  { edgeStart: '--', edgeEnd: '---->', stroke: 'normal', type: 'arrow_point' },
+  { edgeStart: '==', edgeEnd: '====>', stroke: 'thick', type: 'arrow_point' },
+  { edgeStart: '-.', edgeEnd: '...->', stroke: 'dotted', type: 'arrow_point' },
+];
 
 describe('[Edges] when parsing', () => {
   beforeEach(function () {
@@ -67,6 +88,74 @@ describe('[Edges] when parsing', () => {
     expect(edges[0].type).toBe('arrow_circle');
   });
 
+  describe('edges with ids', function () {
+    describe('open ended edges with ids and labels', function () {
+      regularEdges.forEach((edgeType) => {
+        it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
+          const res = flow.parser.parse(
+            `flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
+          );
+          const vert = flow.parser.yy.getVertices();
+          const edges = flow.parser.yy.getEdges();
+          expect(vert.get('A').id).toBe('A');
+          expect(vert.get('B').id).toBe('B');
+          expect(edges.length).toBe(1);
+          expect(edges[0].id).toBe('e1');
+          expect(edges[0].start).toBe('A');
+          expect(edges[0].end).toBe('B');
+          expect(edges[0].type).toBe(`${edgeType.type}`);
+          expect(edges[0].text).toBe('');
+          expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
+        });
+        it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
+          const res = flow.parser.parse(
+            `flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
+          );
+          const vert = flow.parser.yy.getVertices();
+          const edges = flow.parser.yy.getEdges();
+          expect(vert.get('A').id).toBe('A');
+          expect(vert.get('B').id).toBe('B');
+          expect(edges.length).toBe(1);
+          expect(edges[0].id).toBe('e1');
+          expect(edges[0].start).toBe('A');
+          expect(edges[0].end).toBe('B');
+          expect(edges[0].type).toBe(`${edgeType.type}`);
+          expect(edges[0].text).toBe('');
+          expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
+        });
+      });
+      it('should handle normal edges where you also have a node with metadata', function () {
+        const res = flow.parser.parse(`flowchart LR
+A id1@-->B
+A@{ shape: 'rect' }
+`);
+        const edges = flow.parser.yy.getEdges();
+
+        expect(edges[0].id).toBe('id1');
+      });
+    });
+    describe('double ended edges with ids and labels', function () {
+      doubleEndedEdges.forEach((edgeType) => {
+        it(`should handle ${edgeType.stroke} ${edgeType.type} with  text`, function () {
+          const res = flow.parser.parse(
+            `flowchart TD;\nA e1@${edgeType.edgeStart} label ${edgeType.edgeEnd} B;`
+          );
+          const vert = flow.parser.yy.getVertices();
+          const edges = flow.parser.yy.getEdges();
+          expect(vert.get('A').id).toBe('A');
+          expect(vert.get('B').id).toBe('B');
+          expect(edges.length).toBe(1);
+          expect(edges[0].id).toBe('e1');
+          expect(edges[0].start).toBe('A');
+          expect(edges[0].end).toBe('B');
+          expect(edges[0].type).toBe(`${edgeType.type}`);
+          expect(edges[0].text).toBe('label');
+          expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
+        });
+      });
+    });
+  });
+
   describe('edges', function () {
     doubleEndedEdges.forEach((edgeType) => {
       it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
index b3df82fa5..fbd30fa9e 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
@@ -141,6 +141,7 @@ that id.
 .*direction\s+RL[^\n]*       return 'direction_rl';
 .*direction\s+LR[^\n]*       return 'direction_lr';
 
+[^\s]+\@(?=[^\{])               { return 'LINK_ID'; }
 [0-9]+                       return 'NUM';
 \#                           return 'BRKT';
 ":::"                        return 'STYLE_SEPARATOR';
@@ -201,7 +202,9 @@ that id.
 "*"                   return 'MULT';
 "#"                   return 'BRKT';
 "&"                   return 'AMP';
-([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+  return 'NODE_STRING';
+([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+  {
+    return 'NODE_STRING';
+}
 "-"                   return 'MINUS'
 [\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
 [\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
@@ -361,7 +364,7 @@ spaceList
 
 statement
     : vertexStatement separator
-    { /* console.warn('finat vs', $vertexStatement.nodes); */ $$=$vertexStatement.nodes}
+    { $$=$vertexStatement.nodes}
     | styleStatement separator
     {$$=[];}
     | linkStyleStatement separator
@@ -472,6 +475,8 @@ link: linkStatement arrowText
     {$$ = $linkStatement;}
     | START_LINK edgeText LINK
         {var inf = yy.destructLink($LINK, $START_LINK); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$edgeText};}
+    | LINK_ID START_LINK edgeText LINK
+        {var inf = yy.destructLink($LINK, $START_LINK); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$edgeText, "id": $LINK_ID};}
     ;
 
 edgeText: edgeTextToken
@@ -487,6 +492,8 @@ edgeText: edgeTextToken
 
 linkStatement: LINK
         {var inf = yy.destructLink($LINK);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length};}
+    | LINK_ID LINK
+        {var inf = yy.destructLink($LINK);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length, "id": $LINK_ID};}
         ;
 
 arrowText:
diff --git a/packages/mermaid/src/diagrams/flowchart/types.ts b/packages/mermaid/src/diagrams/flowchart/types.ts
index b2c5cf620..00acb6751 100644
--- a/packages/mermaid/src/diagrams/flowchart/types.ts
+++ b/packages/mermaid/src/diagrams/flowchart/types.ts
@@ -62,6 +62,10 @@ export interface FlowEdge {
   length?: number;
   text: string;
   labelType: 'text';
+  classes: string[];
+  id?: string;
+  animation?: 'fast' | 'slow';
+  animate?: boolean;
 }
 
 export interface FlowClass {
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
index a6a7a55f7..2581d342f 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js
+++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
@@ -9,6 +9,7 @@ import { curveBasis, line, select } from 'd3';
 import rough from 'roughjs';
 import createLabel from './createLabel.js';
 import { addEdgeMarkers } from './edgeMarker.ts';
+import { isLabelStyle } from './shapes/handDrawnShapeStyles.js';
 
 const edgeLabels = new Map();
 const terminalLabels = new Map();
@@ -429,6 +430,14 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
   const tail = startNode;
   var head = endNode;
 
+  const edgeClassStyles = [];
+  for (const key in edge.cssCompiledStyles) {
+    if (isLabelStyle(key)) {
+      continue;
+    }
+    edgeClassStyles.push(edge.cssCompiledStyles[key]);
+  }
+
   if (head.intersect && tail.intersect) {
     points = points.slice(1, edge.points.length - 1);
     points.unshift(tail.intersect(points[0]));
@@ -521,12 +530,27 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
     svgPath.attr('d', d);
     elem.node().appendChild(svgPath.node());
   } else {
+    const stylesFromClasses = edgeClassStyles.join(';');
+    const styles = edge.edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '';
+    let animationClass = '';
+    if (edge.animate) {
+      animationClass = ' edge-animation-fast';
+    }
+    if (edge.animation) {
+      animationClass = ' edge-animation-' + edge.animation;
+    }
     svgPath = elem
       .append('path')
       .attr('d', linePath)
       .attr('id', edge.id)
-      .attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
-      .attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
+      .attr(
+        'class',
+        ' ' +
+          strokeClasses +
+          (edge.classes ? ' ' + edge.classes : '') +
+          (animationClass ? animationClass : '')
+      )
+      .attr('style', stylesFromClasses + ';' + styles);
   }
 
   // DEBUG code, DO NOT REMOVE
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.ts
index 80e2a4423..4ac6b2ddd 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.ts
@@ -32,7 +32,28 @@ export const styles2Map = (styles: string[]) => {
   });
   return styleMap;
 };
-
+export const isLabelStyle = (key: string) => {
+  return (
+    key === 'color' ||
+    key === 'font-size' ||
+    key === 'font-family' ||
+    key === 'font-weight' ||
+    key === 'font-style' ||
+    key === 'text-decoration' ||
+    key === 'text-align' ||
+    key === 'text-transform' ||
+    key === 'line-height' ||
+    key === 'letter-spacing' ||
+    key === 'word-spacing' ||
+    key === 'text-shadow' ||
+    key === 'text-overflow' ||
+    key === 'white-space' ||
+    key === 'word-wrap' ||
+    key === 'word-break' ||
+    key === 'overflow-wrap' ||
+    key === 'hyphens'
+  );
+};
 export const styles2String = (node: Node) => {
   const { stylesArray } = compileStyles(node);
   const labelStyles: string[] = [];
@@ -42,26 +63,7 @@ export const styles2String = (node: Node) => {
 
   stylesArray.forEach((style) => {
     const key = style[0];
-    if (
-      key === 'color' ||
-      key === 'font-size' ||
-      key === 'font-family' ||
-      key === 'font-weight' ||
-      key === 'font-style' ||
-      key === 'text-decoration' ||
-      key === 'text-align' ||
-      key === 'text-transform' ||
-      key === 'line-height' ||
-      key === 'letter-spacing' ||
-      key === 'word-spacing' ||
-      key === 'text-shadow' ||
-      key === 'text-overflow' ||
-      key === 'white-space' ||
-      key === 'word-wrap' ||
-      key === 'word-break' ||
-      key === 'overflow-wrap' ||
-      key === 'hyphens'
-    ) {
+    if (isLabelStyle(key)) {
       labelStyles.push(style.join(':') + ' !important');
     } else {
       nodeStyles.push(style.join(':') + ' !important');
diff --git a/packages/mermaid/src/rendering-util/types.ts b/packages/mermaid/src/rendering-util/types.ts
index 86cfd50b3..d64594218 100644
--- a/packages/mermaid/src/rendering-util/types.ts
+++ b/packages/mermaid/src/rendering-util/types.ts
@@ -101,6 +101,7 @@ export interface Edge {
   arrowheadStyle?: string;
   arrowTypeEnd?: string;
   arrowTypeStart?: string;
+  cssCompiledStyles?: string[];
   // Flowchart specific properties
   defaultInterpolate?: string;
   end?: string;
diff --git a/packages/mermaid/src/styles.ts b/packages/mermaid/src/styles.ts
index 78b514c40..2cb11f146 100644
--- a/packages/mermaid/src/styles.ts
+++ b/packages/mermaid/src/styles.ts
@@ -27,7 +27,28 @@ const getStyles = (
     font-size: ${options.fontSize};
     fill: ${options.textColor}
   }
-
+  @keyframes edge-animation-frame {
+    from {
+      stroke-dashoffset: 0;
+    }
+  }
+  @keyframes dash {
+    to {
+      stroke-dashoffset: 0;
+    }
+  }
+  & .edge-animation-slow {
+    stroke-dasharray: 9,5 !important;
+    stroke-dashoffset: 900;
+    animation: dash 50s linear infinite;
+    stroke-linecap: round;
+  }
+  & .edge-animation-fast {
+    stroke-dasharray: 9,5 !important;
+    stroke-dashoffset: 900;
+    animation: dash 20s linear infinite;
+    stroke-linecap: round;
+  }
   /* Classes common for multiple diagrams */
 
   & .error-icon {
diff --git a/packages/mermaid/src/types.ts b/packages/mermaid/src/types.ts
index 5587ca3f4..fdccae677 100644
--- a/packages/mermaid/src/types.ts
+++ b/packages/mermaid/src/types.ts
@@ -12,6 +12,11 @@ export interface NodeMetaData {
   assigned?: string;
   ticket?: string;
 }
+
+export interface EdgeMetaData {
+  animation?: 'fast' | 'slow';
+  animate?: boolean;
+}
 import type { MermaidConfig } from './config.type.js';
 
 export interface Point {
diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts
index c1d674834..68b5e2889 100644
--- a/packages/mermaid/src/utils.ts
+++ b/packages/mermaid/src/utils.ts
@@ -937,8 +937,12 @@ export const getEdgeId = (
     counter?: number;
     prefix?: string;
     suffix?: string;
-  }
+  },
+  id?: string
 ) => {
+  if (id) {
+    return id;
+  }
   return `${prefix ? `${prefix}_` : ''}${from}_${to}_${counter}${suffix ? `_${suffix}` : ''}`;
 };