diff --git a/.changeset/brave-memes-flash.md b/.changeset/brave-memes-flash.md
new file mode 100644
index 000000000..720cd7202
--- /dev/null
+++ b/.changeset/brave-memes-flash.md
@@ -0,0 +1,5 @@
+---
+'mermaid': patch
+---
+
+fix: Support edge animation in hand drawn look
diff --git a/.changeset/busy-mirrors-try.md b/.changeset/busy-mirrors-try.md
new file mode 100644
index 000000000..7e5d3b632
--- /dev/null
+++ b/.changeset/busy-mirrors-try.md
@@ -0,0 +1,5 @@
+---
+'mermaid': patch
+---
+
+fix: Resolved parsing error where direction TD was not recognized within subgraphs
diff --git a/.changeset/curly-apes-prove.md b/.changeset/curly-apes-prove.md
new file mode 100644
index 000000000..2acf3d1a3
--- /dev/null
+++ b/.changeset/curly-apes-prove.md
@@ -0,0 +1,5 @@
+---
+'mermaid': patch
+---
+
+fix: Improve participant parsing and prevent recursive loops on invalid syntax
diff --git a/cypress/helpers/util.ts b/cypress/helpers/util.ts
index ab4bbef64..cf0e9a87d 100644
--- a/cypress/helpers/util.ts
+++ b/cypress/helpers/util.ts
@@ -6,6 +6,7 @@ interface CypressConfig {
listUrl?: boolean;
listId?: string;
name?: string;
+ screenshot?: boolean;
}
type CypressMermaidConfig = MermaidConfig & CypressConfig;
@@ -90,7 +91,7 @@ export const renderGraph = (
export const openURLAndVerifyRendering = (
url: string,
- options: CypressMermaidConfig,
+ { screenshot = true, ...options }: CypressMermaidConfig,
validation?: any
): void => {
const name: string = (options.name ?? cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
@@ -103,7 +104,9 @@ export const openURLAndVerifyRendering = (
cy.get('svg').should(validation);
}
- verifyScreenshot(name);
+ if (screenshot) {
+ verifyScreenshot(name);
+ }
};
export const verifyScreenshot = (name: string): void => {
diff --git a/cypress/integration/rendering/flowchart-handDrawn.spec.js b/cypress/integration/rendering/flowchart-handDrawn.spec.js
index 49c55c628..d3ca1d1f1 100644
--- a/cypress/integration/rendering/flowchart-handDrawn.spec.js
+++ b/cypress/integration/rendering/flowchart-handDrawn.spec.js
@@ -1029,4 +1029,19 @@ graph TD
}
);
});
+
+ it('FDH49: should add edge animation', () => {
+ renderGraph(
+ `
+ flowchart TD
+ A(["Start"]) L_A_B_0@--> B{"Decision"}
+ B --> C["Option A"] & D["Option B"]
+ style C stroke-width:4px,stroke-dasharray: 5
+ L_A_B_0@{ animation: slow }
+ L_B_D_0@{ animation: fast }`,
+ { look: 'handDrawn', screenshot: false }
+ );
+ cy.get('path#L_A_B_0').should('have.class', 'edge-animation-slow');
+ cy.get('path#L_B_D_0').should('have.class', 'edge-animation-fast');
+ });
});
diff --git a/cypress/integration/rendering/flowchart.spec.js b/cypress/integration/rendering/flowchart.spec.js
index 40713ac4e..5e1984377 100644
--- a/cypress/integration/rendering/flowchart.spec.js
+++ b/cypress/integration/rendering/flowchart.spec.js
@@ -774,6 +774,21 @@ describe('Graph', () => {
expect(svg).to.not.have.attr('style');
});
});
+ it('40: should add edge animation', () => {
+ renderGraph(
+ `
+ flowchart TD
+ A(["Start"]) L_A_B_0@--> B{"Decision"}
+ B --> C["Option A"] & D["Option B"]
+ style C stroke-width:4px,stroke-dasharray: 5
+ L_A_B_0@{ animation: slow }
+ L_B_D_0@{ animation: fast }`,
+ { screenshot: false }
+ );
+ // Verify animation classes are applied to both edges
+ cy.get('path#L_A_B_0').should('have.class', 'edge-animation-slow');
+ cy.get('path#L_B_D_0').should('have.class', 'edge-animation-fast');
+ });
it('58: handle styling with style expressions', () => {
imgSnapshotTest(
`
@@ -973,4 +988,19 @@ graph TD
}
);
});
+
+ it('70: should render a subgraph with direction TD', () => {
+ imgSnapshotTest(
+ `
+ flowchart LR
+ subgraph A
+ direction TD
+ a --> b
+ end
+ `,
+ {
+ fontFamily: 'courier',
+ }
+ );
+ });
});
diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md
index 955cc2a7b..110f4eff1 100644
--- a/docs/syntax/classDiagram.md
+++ b/docs/syntax/classDiagram.md
@@ -21,7 +21,7 @@ title: Animal example
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
- note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
+ note for Duck "can fly
can swim
can dive
can help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
@@ -50,7 +50,7 @@ title: Animal example
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
- note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
+ note for Duck "can fly
can swim
can dive
can help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
index 7b2f4386a..7340cf8d3 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
@@ -140,6 +140,7 @@ that id.
.*direction\s+BT[^\n]* return 'direction_bt';
.*direction\s+RL[^\n]* return 'direction_rl';
.*direction\s+LR[^\n]* return 'direction_lr';
+.*direction\s+TD[^\n]* return 'direction_td';
[^\s\"]+\@(?=[^\{\"]) { return 'LINK_ID'; }
[0-9]+ return 'NUM';
@@ -626,6 +627,8 @@ direction
{ $$={stmt:'dir', value:'RL'};}
| direction_lr
{ $$={stmt:'dir', value:'LR'};}
+ | direction_td
+ { $$={stmt:'dir', value:'TD'};}
;
%%
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js
index 9339a6e2c..1273c4d7f 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js
@@ -309,4 +309,21 @@ describe('when parsing subgraphs', function () {
expect(subgraphA.nodes).toContain('a');
expect(subgraphA.nodes).not.toContain('c');
});
+ it('should correctly parse direction TD inside a subgraph', function () {
+ const res = flow.parser.parse(`
+ graph LR
+ subgraph WithTD
+ direction TD
+ A1 --> A2
+ end
+ `);
+
+ const subgraphs = flow.parser.yy.getSubGraphs();
+ expect(subgraphs.length).toBe(1);
+ const subgraph = subgraphs[0];
+
+ expect(subgraph.dir).toBe('TD');
+ expect(subgraph.nodes).toContain('A1');
+ expect(subgraph.nodes).toContain('A2');
+ });
});
diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
index f1364895b..214189a5b 100644
--- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
+++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
@@ -32,13 +32,14 @@
[^\}]+ { return 'CONFIG_CONTENT'; }
\} { this.popState(); this.popState(); return 'CONFIG_END'; }
[^\<->\->:\n,;@\s]+(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; }
-[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
+[^<>:\n,;@\s]+(?=\s+as\s) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
+[^<>:\n,;@]+(?=\s*[\n;#]|$) { yytext = yytext.trim(); this.popState(); return 'ACTOR'; }
+[^<>:\n,;@]*\<[^\n]* { this.popState(); return 'INVALID'; }
"box" { this.begin('LINE'); return 'box'; }
"participant" { this.begin('ID'); return 'participant'; }
"actor" { this.begin('ID'); return 'participant_actor'; }
"create" return 'create';
"destroy" { this.begin('ID'); return 'destroy'; }
-[^<\->\->:\n,;]+?([\-]*[^<\->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
"loop" { this.begin('LINE'); return 'loop'; }
@@ -145,6 +146,7 @@ line
: SPACE statement { $$ = $2 }
| statement { $$ = $1 }
| NEWLINE { $$=[]; }
+ | INVALID { $$=[]; }
;
box_section
@@ -411,4 +413,4 @@ text2
: TXT {$$ = yy.parseMessage($1.trim().substring(1)) }
;
-%%
+%%
\ No newline at end of file
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
index 5f4e06dcd..cabcd7db5 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
+++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
@@ -2609,5 +2609,17 @@ Bob->>Alice:Got it!
expect(actors.get('E').type).toBe('entity');
expect(actors.get('E').description).toBe('E');
});
+ it('should handle fail parsing when alias token causes conflicts in participant definition', async () => {
+ let error = false;
+ try {
+ await Diagram.fromText(`
+ sequenceDiagram
+ participant SAS MyServiceWithMoreThan20Chars
service decription
+ `);
+ } catch (e) {
+ error = true;
+ }
+ expect(error).toBe(true);
+ });
});
});
diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md
index e62433283..367030d8e 100644
--- a/packages/mermaid/src/docs/syntax/classDiagram.md
+++ b/packages/mermaid/src/docs/syntax/classDiagram.md
@@ -15,7 +15,7 @@ title: Animal example
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
- note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
+ note for Duck "can fly
can swim
can dive
can help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
index 9e308631a..81cd79d46 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js
+++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
@@ -605,6 +605,14 @@ export const insertEdge = function (
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
let strokeColor = edgeStyles.find((style) => style?.startsWith('stroke:'));
+ let animationClass = '';
+ if (edge.animate) {
+ animationClass = 'edge-animation-fast';
+ }
+ if (edge.animation) {
+ animationClass = 'edge-animation-' + edge.animation;
+ }
+
let animatedEdge = false;
if (edge.look === 'handDrawn') {
const rc = rough.svg(elem);
@@ -620,7 +628,13 @@ export const insertEdge = function (
svgPath = select(svgPathNode)
.select('path')
.attr('id', edge.id)
- .attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
+ .attr(
+ 'class',
+ ' ' +
+ strokeClasses +
+ (edge.classes ? ' ' + edge.classes : '') +
+ (animationClass ? ' ' + animationClass : '')
+ )
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
let d = svgPath.attr('d');
svgPath.attr('d', d);
@@ -628,13 +642,6 @@ export const insertEdge = function (
} else {
const stylesFromClasses = edgeClassStyles.join(';');
const styles = edgeStyles ? edgeStyles.reduce((acc, style) => acc + style + ';', '') : '';
- let animationClass = '';
- if (edge.animate) {
- animationClass = ' edge-animation-fast';
- }
- if (edge.animation) {
- animationClass = ' edge-animation-' + edge.animation;
- }
const pathStyle =
(stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles) +
@@ -646,7 +653,10 @@ export const insertEdge = function (
.attr('id', edge.id)
.attr(
'class',
- ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '') + (animationClass ?? '')
+ ' ' +
+ strokeClasses +
+ (edge.classes ? ' ' + edge.classes : '') +
+ (animationClass ? ' ' + animationClass : '')
)
.attr('style', pathStyle);