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/chilly-words-march.md b/.changeset/chilly-words-march.md
new file mode 100644
index 000000000..54c0b4ebf
--- /dev/null
+++ b/.changeset/chilly-words-march.md
@@ -0,0 +1,5 @@
+---
+'mermaid': patch
+---
+
+fix: Correct viewBox casing and make SVGs responsive
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..d02e9da87 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, '-');
@@ -98,12 +99,15 @@ export const openURLAndVerifyRendering = (
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
+ cy.get('svg').should('not.have.attr', 'viewbox');
if (validation) {
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/cypress/platform/yari.html b/cypress/platform/yari.html
index 390218344..893d4c7b1 100644
--- a/cypress/platform/yari.html
+++ b/cypress/platform/yari.html
@@ -603,6 +603,10 @@
+ ---
+ config:
+ theme: dark
+ ---
classDiagram
test ()--() test2
diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md
index 7dc9a1024..899081114 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/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts
index 41575a33f..c3edaf80e 100644
--- a/packages/mermaid/src/diagrams/class/classDb.ts
+++ b/packages/mermaid/src/diagrams/class/classDb.ts
@@ -631,7 +631,7 @@ export class ClassDB implements DiagramDB {
padding: config.class!.padding ?? 16,
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
shape: 'rect',
- cssStyles: ['fill: none', 'stroke: black'],
+ cssStyles: [],
look: config.look,
};
nodes.push(node);
diff --git a/packages/mermaid/src/diagrams/class/styles.js b/packages/mermaid/src/diagrams/class/styles.js
index ef22e28d1..5158dd738 100644
--- a/packages/mermaid/src/diagrams/class/styles.js
+++ b/packages/mermaid/src/diagrams/class/styles.js
@@ -13,6 +13,30 @@ const getStyles = (options) =>
}
+ .cluster-label text {
+ fill: ${options.titleColor};
+ }
+ .cluster-label span {
+ color: ${options.titleColor};
+ }
+ .cluster-label span p {
+ background-color: transparent;
+ }
+
+ .cluster rect {
+ fill: ${options.clusterBkg};
+ stroke: ${options.clusterBorder};
+ stroke-width: 1px;
+ }
+
+ .cluster text {
+ fill: ${options.titleColor};
+ }
+
+ .cluster span {
+ color: ${options.titleColor};
+ }
+
.nodeLabel, .edgeLabel {
color: ${options.classText};
}
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/packet/renderer.ts b/packages/mermaid/src/diagrams/packet/renderer.ts
index 25445a228..95226cf12 100644
--- a/packages/mermaid/src/diagrams/packet/renderer.ts
+++ b/packages/mermaid/src/diagrams/packet/renderer.ts
@@ -16,7 +16,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
const svgWidth = bitWidth * bitsPerRow + 2;
const svg: SVG = selectSvgElement(id);
- svg.attr('viewbox', `0 0 ${svgWidth} ${svgHeight}`);
+ svg.attr('viewBox', `0 0 ${svgWidth} ${svgHeight}`);
configureSvgSize(svg, svgHeight, svgWidth, config.useMaxWidth);
for (const [word, packet] of words.entries()) {
diff --git a/packages/mermaid/src/diagrams/radar/renderer.ts b/packages/mermaid/src/diagrams/radar/renderer.ts
index 7324a3a44..247ded66d 100644
--- a/packages/mermaid/src/diagrams/radar/renderer.ts
+++ b/packages/mermaid/src/diagrams/radar/renderer.ts
@@ -2,6 +2,7 @@ import type { Diagram } from '../../Diagram.js';
import type { RadarDiagramConfig } from '../../config.type.js';
import type { DiagramRenderer, DrawDefinition, SVG, SVGGroup } from '../../diagram-api/types.js';
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
+import { configureSvgSize } from '../../setupGraphViewbox.js';
import type { RadarDB, RadarAxis, RadarCurve } from './types.js';
const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
@@ -53,11 +54,9 @@ const drawFrame = (svg: SVG, config: Required
): SVGGroup =>
x: config.marginLeft + config.width / 2,
y: config.marginTop + config.height / 2,
};
- // Initialize the SVG
- svg
- .attr('viewbox', `0 0 ${totalWidth} ${totalHeight}`)
- .attr('width', totalWidth)
- .attr('height', totalHeight);
+ configureSvgSize(svg, totalHeight, totalWidth, config.useMaxWidth ?? true);
+
+ svg.attr('viewBox', `0 0 ${totalWidth} ${totalHeight}`);
// g element to center the radar chart
return svg.append('g').attr('transform', `translate(${center.x}, ${center.y})`);
};
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 b610828d1..28cefd667 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);
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/markers.js b/packages/mermaid/src/rendering-util/rendering-elements/markers.js
index 0566a4dcc..e753debc4 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/markers.js
+++ b/packages/mermaid/src/rendering-util/rendering-elements/markers.js
@@ -130,7 +130,6 @@ const lollipop = (elem, type, id) => {
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('circle')
- .attr('stroke', 'black')
.attr('fill', 'transparent')
.attr('cx', 7)
.attr('cy', 7)
@@ -147,7 +146,6 @@ const lollipop = (elem, type, id) => {
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('circle')
- .attr('stroke', 'black')
.attr('fill', 'transparent')
.attr('cx', 7)
.attr('cy', 7)