From cc476d59d1440d919f5a32ab4a393fd93138a926 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 27 Jun 2025 13:42:19 +0530 Subject: [PATCH 01/46] 6637-Add new participant types to Sequence Diagrams --- .../sequence/parser/sequenceDiagram.jison | 25 + .../src/diagrams/sequence/sequenceDb.ts | 11 + .../src/diagrams/sequence/sequenceRenderer.ts | 12 +- .../mermaid/src/diagrams/sequence/styles.js | 6 + .../mermaid/src/diagrams/sequence/svgDraw.js | 615 ++++++++++++++++++ 5 files changed, 666 insertions(+), 3 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index d2e81df5f..4b5a82851 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -31,6 +31,12 @@ "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } "actor" { this.begin('ID'); return 'participant_actor'; } +"boundary" { this.begin('ID'); return 'participant_boundary'; } +"control" { this.begin('ID'); return 'participant_control'; } +"entity" { this.begin('ID'); return 'participant_entity'; } +"database" { this.begin('ID'); return 'participant_database'; } +"collections" { this.begin('ID'); return 'participant_collections'; } +"queue" { this.begin('ID'); return 'participant_queue'; } "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'; } @@ -231,6 +237,25 @@ participant_statement | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} + + | 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_boundary' actor 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant'; $$=$2;} + + | 'participant_control' actor 'AS' restOfLine 'NEWLINE' {$2.draw='control'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_control' actor 'NEWLINE' {$2.draw='control'; $2.type='addParticipant'; $$=$2;} + + | 'participant_entity' actor 'AS' restOfLine 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_entity' actor 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant'; $$=$2;} + + | 'participant_database' actor 'AS' restOfLine 'NEWLINE' {$2.draw='database'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_database' actor 'NEWLINE' {$2.draw='database'; $2.type='addParticipant'; $$=$2;} + + | 'participant_collections' actor 'AS' restOfLine 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_collections' actor 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant'; $$=$2;} + + | 'participant_queue' actor 'AS' restOfLine 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_queue' actor 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant'; $$=$2;} + ; note_statement diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index c6b44dac0..d8421b84d 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -75,6 +75,17 @@ const PLACEMENT = { OVER: 2, } as const; +export const PARTICIPANT_TYPE = { + ACTOR: 'actor', + BOUNDARY: 'boundary', + COLLECTIONS: 'collections', + CONTROL: 'control', + DATABASE: 'database', + ENTITY: 'entity', + PARTICIPANT: 'participant', + QUEUE: 'queue', +} as const; + export class SequenceDB implements DiagramDB { private readonly state = new ImperativeState(() => ({ prevActor: undefined, diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index cfba92b79..8b4842fc1 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth.js'; import utils from '../../utils.js'; import { configureSvgSize } from '../../setupGraphViewbox.js'; import type { Diagram } from '../../Diagram.js'; +import { PARTICIPANT_TYPE } from './sequenceDb.js'; let conf = {}; @@ -724,11 +725,14 @@ function adjustCreatedDestroyedData( msgModel.startx = msgModel.startx - adjustment; } } + const actorArray = [PARTICIPANT_TYPE.ACTOR, PARTICIPANT_TYPE.CONTROL, PARTICIPANT_TYPE.ENTITY]; // if it is a create message if (createdActors.get(msg.to) == index) { const actor = actors.get(msg.to); - const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; + const adjustment = actorArray.includes(actor.type) + ? ACTOR_TYPE_WIDTH / 2 + 3 + : actor.width / 2 + 3; receiverAdjustment(actor, adjustment); actor.starty = lineStartY - actor.height / 2; bounds.bumpVerticalPos(actor.height / 2); @@ -737,7 +741,7 @@ function adjustCreatedDestroyedData( else if (destroyedActors.get(msg.from) == index) { const actor = actors.get(msg.from); if (conf.mirrorActors) { - const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2; + const adjustment = actorArray.includes(actor.type) ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2; senderAdjustment(actor, adjustment); } actor.stopy = lineStartY - actor.height / 2; @@ -747,7 +751,9 @@ function adjustCreatedDestroyedData( else if (destroyedActors.get(msg.to) == index) { const actor = actors.get(msg.to); if (conf.mirrorActors) { - const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; + const adjustment = actorArray.includes(actor.type) + ? ACTOR_TYPE_WIDTH / 2 + 3 + : actor.width / 2 + 3; receiverAdjustment(actor, adjustment); } actor.stopy = lineStartY - actor.height / 2; diff --git a/packages/mermaid/src/diagrams/sequence/styles.js b/packages/mermaid/src/diagrams/sequence/styles.js index 5c36b4ed1..3cee9d3dc 100644 --- a/packages/mermaid/src/diagrams/sequence/styles.js +++ b/packages/mermaid/src/diagrams/sequence/styles.js @@ -12,6 +12,11 @@ const getStyles = (options) => .actor-line { stroke: ${options.actorLineColor}; } + + .innerArc { + stroke-width: 1.5; + stroke-dasharray: none; + } .messageLine0 { stroke-width: 1.5; @@ -115,6 +120,7 @@ const getStyles = (options) => fill: ${options.actorBkg}; stroke-width: 2px; } + `; export default getStyles; diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index c681c9491..3af3d3cf0 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -3,6 +3,8 @@ import * as svgDrawCommon from '../common/svgDrawCommon.js'; import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js'; import { sanitizeUrl } from '@braintree/sanitize-url'; import * as configApi from '../../config.js'; +import { createInnerCylinderPathD } from '../../rendering-util/rendering-elements/shapes/tiltedCylinder.js'; +import { createCylinderPathD } from '../../rendering-util/rendering-elements/shapes/cylinder.js'; export const ACTOR_TYPE_WIDTH = 18 * 2; const TOP_ACTOR_CLASS = 'actor-top'; @@ -411,6 +413,607 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { return height; }; +/** + * Draws an actor in the diagram with the attached line + * + * @param {any} elem - The diagram we'll draw to. + * @param {any} actor - The actor to draw. + * @param {any} conf - DrawText implementation discriminator object + * @param {boolean} isFooter - If the actor is the footer one + */ +const drawActorTypeCollections = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + actor.height; + + const boxplusLineGroup = elem.append('g').lower(); + var g = boxplusLineGroup; + + if (!isFooter) { + actorCnt++; + if (Object.keys(actor.links || {}).length && !conf.forceMenus) { + g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); + } + g.append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + g = boxplusLineGroup.append('g'); + actor.actorCnt = actorCnt; + + if (actor.links != null) { + g.attr('id', 'root-' + actorCnt); + } + } + + const rect = svgDrawCommon.getNoteRect(); + var cssclass = 'actor'; + if (actor.properties?.class) { + cssclass = actor.properties.class; + } else { + rect.fill = '#eaeaea'; + } + if (isFooter) { + cssclass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssclass += ` ${TOP_ACTOR_CLASS}`; + } + rect.x = actor.x; + rect.y = actorY; + rect.width = actor.width; + rect.height = actor.height; + rect.class = cssclass; + // rect.rx = 3; + // rect.ry = 3; + rect.name = actor.name; + + // 🔹 DRAW STACKED RECTANGLES + const offset = 6; + const shadowRect = { + ...rect, + x: rect.x + offset, + y: rect.y + (isFooter ? +offset : -offset), + class: 'actor', + }; + drawRect(g, shadowRect); + const rectElem = drawRect(g, rect); // draw main rectangle on top + actor.rectData = rect; + + if (actor.properties?.icon) { + const iconSrc = actor.properties.icon.trim(); + if (iconSrc.charAt(0) === '@') { + svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1)); + } else { + svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc); + } + } + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + g, + rect.x, + rect.y, + rect.width, + rect.height, + { class: `actor ${ACTOR_BOX_CLASS}` }, + conf + ); + + let height = actor.height; + if (rectElem.node) { + const bounds = rectElem.node().getBBox(); + actor.height = bounds.height; + height = bounds.height; + } + + return height; +}; + +const drawActorTypeQueue = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + actor.height; + + const boxplusLineGroup = elem.append('g').lower(); + let g = boxplusLineGroup; + + if (!isFooter) { + actorCnt++; + if (Object.keys(actor.links || {}).length && !conf.forceMenus) { + g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); + } + g.append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + g = boxplusLineGroup.append('g'); + actor.actorCnt = actorCnt; + + if (actor.links != null) { + g.attr('id', 'root-' + actorCnt); + } + } + + const rect = svgDrawCommon.getNoteRect(); + let cssclass = 'actor'; + if (actor.properties?.class) { + cssclass = actor.properties.class; + } else { + rect.fill = '#eaeaea'; + } + + if (isFooter) { + cssclass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssclass += ` ${TOP_ACTOR_CLASS}`; + } + + rect.x = actor.x; + rect.y = actorY; + rect.width = actor.width; + rect.height = actor.height; + rect.class = cssclass; + rect.name = actor.name; + + // Cylinder dimensions + const ry = rect.height / 2; + const rx = ry / (2.5 + rect.height / 50); + + // Cylinder base group + const cylinderGroup = g.append('g'); + const cylinderArc = g.append('g'); + + // Main cylinder body + cylinderGroup + .append('path') + .attr( + 'd', + ` + M ${rect.x},${rect.y + ry} + a ${rx},${ry} 0 0 0 0,${rect.height} + h ${rect.width - 2 * rx} + a ${rx},${ry} 0 0 0 0,-${rect.height} + Z + ` + ) + .attr('class', cssclass); + cylinderArc + .append('path') + .attr('d', createInnerCylinderPathD(rect.x - rx, rect.y + ry, rect.width, rect.height, rx, ry)) + .attr('stroke', '#666') + .attr('stroke-width', '1px') + .attr('class', cssclass); + + if (!isFooter) { + cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2) - rect.y})`); + cylinderArc.attr('transform', `translate(${rect.width / 2}, ${rect.height / 2})`); + } else { + cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`); + cylinderArc.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`); + } + + actor.rectData = rect; + + if (actor.properties?.icon) { + const iconSrc = actor.properties.icon.trim(); + const iconX = rect.x + rect.width - 20; + const iconY = rect.y + 10; + if (iconSrc.charAt(0) === '@') { + svgDrawCommon.drawEmbeddedImage(g, iconX, iconY, iconSrc.substr(1)); + } else { + svgDrawCommon.drawImage(g, iconX, iconY, iconSrc); + } + } + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + g, + rect.x, + rect.y, + rect.width, + rect.height, + { class: `actor ${ACTOR_BOX_CLASS}` }, + conf + ); + + let height = actor.height; + const lastPath = cylinderGroup.select('path:last-child'); + if (lastPath.node()) { + const bounds = lastPath.node().getBBox(); + actor.height = bounds.height; + height = bounds.height; + } + + return height; +}; + +const drawActorTypeControl = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + 75; + + const line = elem.append('g').lower(); + + if (!isFooter) { + actorCnt++; + line + .append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + actor.actorCnt = actorCnt; + } + const actElem = elem.append('g'); + let cssClass = ACTOR_MAN_FIGURE_CLASS; + if (isFooter) { + cssClass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssClass += ` ${TOP_ACTOR_CLASS}`; + } + actElem.attr('class', cssClass); + actElem.attr('name', actor.name); + + const rect = svgDrawCommon.getNoteRect(); + rect.x = actor.x; + rect.y = actorY; + rect.fill = '#eaeaea'; + rect.width = actor.width; + rect.height = actor.height; + rect.class = 'actor'; + + const cx = actor.x + actor.width / 2; + const cy = actorY + 30; + const r = 18; + + actElem + .append('defs') + .append('marker') + .attr('id', 'filled-head-control') + .attr('refX', 15.5) + .attr('refY', 7) + .attr('markerWidth', 20) + .attr('markerHeight', 28) + .attr('orient', '180') + .append('path') + .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); + + // Draw the base circle + actElem + .append('circle') + .attr('cx', cx) + .attr('cy', cy) + .attr('r', r) + .attr('fill', '#eaeaf7') + .attr('stroke', '#666') + .attr('stroke-width', 1.2); + + // Draw looping arrow as arc path + actElem + .append('path') + .attr('d', `M ${cx},${cy - r}`) + .attr('stroke-width', 1.5) + .attr('marker-end', 'url(#filled-head-control)'); + + const bounds = actElem.node().getBBox(); + actor.height = bounds.height + conf.boxTextMargin; + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + actElem, + rect.x, + rect.y + (!isFooter ? 30 : 40), + rect.width, + rect.height, + { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, + conf + ); + + return actor.height; +}; + +const drawActorTypeEntity = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + 75; + + const line = elem.append('g').lower(); + + const actElem = elem.append('g'); + let cssClass = ACTOR_MAN_FIGURE_CLASS; + if (isFooter) { + cssClass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssClass += ` ${TOP_ACTOR_CLASS}`; + } + actElem.attr('class', cssClass); + actElem.attr('name', actor.name); + + const rect = svgDrawCommon.getNoteRect(); + rect.x = actor.x; + rect.y = actorY; + rect.fill = '#eaeaea'; + rect.width = actor.width; + rect.height = actor.height; + rect.class = 'actor'; + + const cx = actor.x + actor.width / 2; + const cy = actorY + 25; + const r = 18; + + actElem + .append('circle') + .attr('cx', cx) + .attr('cy', cy) + .attr('r', r) + .attr('width', actor.width) + .attr('height', actor.height); + + actElem + .append('line') + .attr('x1', cx - r) + .attr('x2', cx + r) + .attr('y1', cy + r) + .attr('y2', cy + r) + .attr('stroke', '#333') + .attr('stroke-width', 2); + + const boundBox = actElem.node().getBBox(); + actor.height = boundBox.height + conf.boxTextMargin; + + if (!isFooter) { + actorCnt++; + line + .append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + actor.actorCnt = actorCnt; + } + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + actElem, + rect.x, + rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY) / 2 + r + 2), + rect.width, + rect.height, + { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, + conf + ); + + if (!isFooter) { + actElem.attr('transform', `translate(${0}, ${r / 2})`); + } else { + actElem.attr('transform', `translate(${0}, ${r / 2})`); + } + + return actor.height; +}; + +const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + actor.height; + + const boxplusLineGroup = elem.append('g').lower(); + let g = boxplusLineGroup; + + if (!isFooter) { + actorCnt++; + if (Object.keys(actor.links || {}).length && !conf.forceMenus) { + g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); + } + g.append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + g = boxplusLineGroup.append('g'); + actor.actorCnt = actorCnt; + + if (actor.links != null) { + g.attr('id', 'root-' + actorCnt); + } + } + + const rect = svgDrawCommon.getNoteRect(); + + let cssclass = 'actor'; + if (actor.properties?.class) { + cssclass = actor.properties.class; + } else { + rect.fill = '#eaeaea'; + } + + if (isFooter) { + cssclass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssclass += ` ${TOP_ACTOR_CLASS}`; + } + + rect.x = actor.x; + rect.y = actorY; + rect.width = actor.width; + rect.height = actor.height; + rect.class = cssclass; + rect.name = actor.name; + + // Cylinder dimensions + rect.x = actor.x; + rect.y = actorY; + const w = rect.width; + const h = rect.height; + const rx = w / 2; + const ry = rx / (2.5 + w / 50); + + // Cylinder base group + const cylinderGroup = g.append('g'); + + const pathData = createCylinderPathD( + rect.x, + rect.y, + w / 2, + isFooter ? h - ry : h / 2, + rx / 4, + ry / 4 + ); + cylinderGroup.append('path').attr('d', pathData).attr('class', cssclass); + + if (!isFooter) { + cylinderGroup.attr('transform', `translate(${w / 4}, ${-centerY / 2 + 2.75 * ry})`); + } else { + cylinderGroup.attr('transform', `translate(${w / 4}, ${ry / 2})`); + } + + actor.rectData = rect; + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + g, + rect.x, + rect.y + (!isFooter ? ry * 1.75 : h / 2 + ry), + rect.width, + rect.height, + { class: `actor ${ACTOR_BOX_CLASS}` }, + conf + ); + + let height = actor.height; + const lastPath = cylinderGroup.select('path:last-child'); + if (lastPath.node()) { + const bounds = lastPath.node().getBBox(); + actor.height = bounds.height; + height = bounds.height; + } + + return height; +}; + +const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + 80; + const radius = 30; + const line = elem.append('g').lower(); + + if (!isFooter) { + actorCnt++; + line + .append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + actor.actorCnt = actorCnt; + } + const actElem = elem.append('g'); + let cssClass = ACTOR_MAN_FIGURE_CLASS; + if (isFooter) { + cssClass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssClass += ` ${TOP_ACTOR_CLASS}`; + } + actElem.attr('class', cssClass); + actElem.attr('name', actor.name); + + const rect = svgDrawCommon.getNoteRect(); + rect.x = actor.x; + rect.y = actorY; + rect.fill = '#eaeaea'; + rect.width = actor.width; + rect.height = actor.height; + rect.class = 'actor'; + rect.rx = 3; + rect.ry = 3; + + actElem + .append('line') + .attr('id', 'actor-man-torso' + actorCnt) + .attr('x1', actor.x + actor.width / 2 - radius * 2.5) + .attr('y1', actorY + 10) + .attr('x2', actor.x + actor.width / 2 - 15) + .attr('y2', actorY + 10); + + actElem + .append('line') + .attr('id', 'actor-man-arms' + actorCnt) + .attr('x1', actor.x + actor.width / 2 - radius * 2.5) + .attr('y1', actorY + 0) // starting Y + .attr('x2', actor.x + actor.width / 2 - radius * 2.5) + .attr('y2', actorY + 20); // ending Y (26px long, adjust as needed) + + actElem + .append('circle') + .attr('cx', actor.x + actor.width / 2) + .attr('cy', actorY + 10) + .attr('r', radius) + .attr('width', actor.width); + + const bounds = actElem.node().getBBox(); + actor.height = bounds.height + conf.boxTextMargin; + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + actElem, + rect.x, + rect.y + (!isFooter ? radius / 2 : radius / 2), + rect.width, + rect.height, + { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, + conf + ); + + if (!isFooter) { + actElem.attr('transform', `translate(0,${radius / 2 + 7})`); + } else { + actElem.attr('transform', `translate(0,${radius / 2 + 7})`); + } + + // actElem.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`); + + return actor.height; +}; + const drawActorTypeActor = function (elem, actor, conf, isFooter) { const actorY = isFooter ? actor.stopy : actor.starty; const center = actor.x + actor.width / 2; @@ -512,6 +1115,18 @@ export const drawActor = async function (elem, actor, conf, isFooter) { return await drawActorTypeActor(elem, actor, conf, isFooter); case 'participant': return await drawActorTypeParticipant(elem, actor, conf, isFooter); + case 'boundary': + return await drawActorTypeBoundary(elem, actor, conf, isFooter); + case 'control': + return await drawActorTypeControl(elem, actor, conf, isFooter); + case 'entity': + return await drawActorTypeEntity(elem, actor, conf, isFooter); + case 'database': + return await drawActorTypeDatabase(elem, actor, conf, isFooter); + case 'collections': + return await drawActorTypeCollections(elem, actor, conf, isFooter); + case 'queue': + return await drawActorTypeQueue(elem, actor, conf, isFooter); } }; From 76d073b0272d5b3694cb754860717b874195ac49 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 27 Jun 2025 17:56:03 +0530 Subject: [PATCH 02/46] chore: adjusted database and queue shape --- .../src/diagrams/sequence/sequenceRenderer.ts | 7 +- .../mermaid/src/diagrams/sequence/svgDraw.js | 65 +++++++++---------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 8b4842fc1..e44431928 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -725,7 +725,12 @@ function adjustCreatedDestroyedData( msgModel.startx = msgModel.startx - adjustment; } } - const actorArray = [PARTICIPANT_TYPE.ACTOR, PARTICIPANT_TYPE.CONTROL, PARTICIPANT_TYPE.ENTITY]; + const actorArray = [ + PARTICIPANT_TYPE.ACTOR, + PARTICIPANT_TYPE.CONTROL, + PARTICIPANT_TYPE.ENTITY, + PARTICIPANT_TYPE.DATABASE, + ]; // if it is a create message if (createdActors.get(msg.to) == index) { diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index 3af3d3cf0..5304f57f5 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -3,8 +3,6 @@ import * as svgDrawCommon from '../common/svgDrawCommon.js'; import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js'; import { sanitizeUrl } from '@braintree/sanitize-url'; import * as configApi from '../../config.js'; -import { createInnerCylinderPathD } from '../../rendering-util/rendering-elements/shapes/tiltedCylinder.js'; -import { createCylinderPathD } from '../../rendering-util/rendering-elements/shapes/cylinder.js'; export const ACTOR_TYPE_WIDTH = 18 * 2; const TOP_ACTOR_CLASS = 'actor-top'; @@ -582,8 +580,7 @@ const drawActorTypeQueue = function (elem, actor, conf, isFooter) { .append('path') .attr( 'd', - ` - M ${rect.x},${rect.y + ry} + `M ${rect.x},${rect.y + ry} a ${rx},${ry} 0 0 0 0,${rect.height} h ${rect.width - 2 * rx} a ${rx},${ry} 0 0 0 0,-${rect.height} @@ -593,18 +590,17 @@ const drawActorTypeQueue = function (elem, actor, conf, isFooter) { .attr('class', cssclass); cylinderArc .append('path') - .attr('d', createInnerCylinderPathD(rect.x - rx, rect.y + ry, rect.width, rect.height, rx, ry)) + .attr( + 'd', + `M ${rect.x},${rect.y + ry} + a ${rx},${ry} 0 0 0 0,${rect.height}` + ) .attr('stroke', '#666') .attr('stroke-width', '1px') .attr('class', cssclass); - if (!isFooter) { - cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2) - rect.y})`); - cylinderArc.attr('transform', `translate(${rect.width / 2}, ${rect.height / 2})`); - } else { - cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`); - cylinderArc.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`); - } + cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`); + cylinderArc.attr('transform', `translate(${rect.width - rx}, ${-rect.height / 2})`); actor.rectData = rect; @@ -821,7 +817,7 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { const actorY = isFooter ? actor.stopy : actor.starty; const center = actor.x + actor.width / 2; - const centerY = actorY + actor.height; + const centerY = actorY + actor.height + 2 * conf.boxTextMargin; const boxplusLineGroup = elem.append('g').lower(); let g = boxplusLineGroup; @@ -875,52 +871,55 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { // Cylinder dimensions rect.x = actor.x; rect.y = actorY; - const w = rect.width; - const h = rect.height; + const w = rect.width / 4; + const h = rect.height * 0.8; const rx = w / 2; const ry = rx / (2.5 + w / 50); // Cylinder base group const cylinderGroup = g.append('g'); - const pathData = createCylinderPathD( - rect.x, - rect.y, - w / 2, - isFooter ? h - ry : h / 2, - rx / 4, - ry / 4 - ); - cylinderGroup.append('path').attr('d', pathData).attr('class', cssclass); + const d = ` + M ${rect.x},${rect.y + ry} + a ${rx},${ry} 0 0 0 ${w},0 + a ${rx},${ry} 0 0 0 -${w},0 + l 0,${h - 2 * ry} + a ${rx},${ry} 0 0 0 ${w},0 + l 0,-${h - 2 * ry} +`; + // Draw the main cylinder body + cylinderGroup + .append('path') + .attr('d', d) + .attr('fill', '#eaeaea') + .attr('stroke', '#000') + .attr('stroke-width', 1) + .attr('class', cssclass); if (!isFooter) { - cylinderGroup.attr('transform', `translate(${w / 4}, ${-centerY / 2 + 2.75 * ry})`); + cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); } else { - cylinderGroup.attr('transform', `translate(${w / 4}, ${ry / 2})`); + cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); } - actor.rectData = rect; - _drawTextCandidateFunc(conf, hasKatex(actor.description))( actor.description, g, rect.x, - rect.y + (!isFooter ? ry * 1.75 : h / 2 + ry), + rect.y + (!isFooter ? rect.height / 2 : h / 2 + ry), rect.width, rect.height, { class: `actor ${ACTOR_BOX_CLASS}` }, conf ); - let height = actor.height; const lastPath = cylinderGroup.select('path:last-child'); if (lastPath.node()) { const bounds = lastPath.node().getBBox(); - actor.height = bounds.height; - height = bounds.height; + actor.height = bounds.height + 2 * conf.boxTextMargin; } - return height; + return actor.height; }; const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { From b61bec8fafee523cc545493023151fcd0c13a45d Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 1 Jul 2025 12:58:04 +0530 Subject: [PATCH 03/46] added test cases for new participant types of sequence diagram --- .../rendering/sequencediagram-v2.spec.js | 644 ++++++++++++++++++ 1 file changed, 644 insertions(+) create mode 100644 cypress/integration/rendering/sequencediagram-v2.spec.js diff --git a/cypress/integration/rendering/sequencediagram-v2.spec.js b/cypress/integration/rendering/sequencediagram-v2.spec.js new file mode 100644 index 000000000..4b7fe88d5 --- /dev/null +++ b/cypress/integration/rendering/sequencediagram-v2.spec.js @@ -0,0 +1,644 @@ +import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; + +const looks = ['classic']; +const participantTypes = [ + 'participant', + 'actor', + 'boundary', + 'control', + 'entity', + 'database', + 'collections', + 'queue', +]; + +const interactionTypes = [ + '->>', // Solid arrow with arrowhead + '-->>', // Dotted arrow with arrowhead + '->', // Solid arrow without arrowhead + '-->', // Dotted arrow without arrowhead + '-x', // Solid arrow with cross + '--x', // Dotted arrow with cross + '->>+', // Solid arrow with arrowhead (activate) + '-->>+', // Dotted arrow with arrowhead (activate) +]; + +const notePositions = ['left of', 'right of', 'over']; + +looks.forEach((look) => { + describe(`Sequence Diagram Tests - ${look} look`, () => { + it('should render all participant types', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.forEach((type, index) => { + diagramCode += ` ${type} ${type}${index} as ${type} ${index}\n`; + }); + // Add some basic interactions + for (let i = 0; i < participantTypes.length - 1; i++) { + diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; + } + imgSnapshotTest(diagramCode, { look, sequence: { diagramMarginX: 50, diagramMarginY: 10 } }); + }); + + it('should render all interaction types', () => { + let diagramCode = `sequenceDiagram\n`; + // Create two participants + // Add all interaction types + diagramCode += ` participant A\n`; + diagramCode += ` participant B\n`; + interactionTypes.forEach((interaction, index) => { + diagramCode += ` A ${interaction} B: ${interaction} message ${index}\n`; + }); + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render participant creation and destruction', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.forEach((type, index) => { + diagramCode += ` ${type} A\n`; + diagramCode += ` ${type} B\n`; + diagramCode += ` create ${type} ${type}${index}\n`; + diagramCode += ` A ->> ${type}${index}: Hello ${type}\n`; + if (index % 2 === 0) { + diagramCode += ` destroy ${type}${index}\n`; + } + }); + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render notes in all positions', () => { + let diagramCode = `sequenceDiagram\n`; + diagramCode += ` participant A\n`; + diagramCode += ` participant B\n`; + notePositions.forEach((position, index) => { + diagramCode += ` Note ${position} A: Note ${position} ${index}\n`; + }); + diagramCode += ` A ->> B: Message with notes\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render parallel interactions', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.slice(0, 4).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` par Parallel actions\n`; + for (let i = 0; i < participantTypes.length - 1; i += 2) { + diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; + if (i < participantTypes.length - 2) { + diagramCode += ` and\n`; + } + } + diagramCode += ` end\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render alternative flows', () => { + let diagramCode = `sequenceDiagram\n`; + diagramCode += ` participant A\n`; + diagramCode += ` participant B\n`; + diagramCode += ` alt Successful case\n`; + diagramCode += ` A ->> B: Request\n`; + diagramCode += ` B -->> A: Success\n`; + diagramCode += ` else Failure case\n`; + diagramCode += ` A ->> B: Request\n`; + diagramCode += ` B --x A: Failure\n`; + diagramCode += ` end\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render loops', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.slice(0, 3).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` loop For each participant\n`; + for (let i = 0; i < 3; i++) { + diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Message ${i}\n`; + } + diagramCode += ` end\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render boxes around groups', () => { + let diagramCode = `sequenceDiagram\n`; + diagramCode += ` box Group 1\n`; + participantTypes.slice(0, 3).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` end\n`; + diagramCode += ` box rgb(200,220,255) Group 2\n`; + participantTypes.slice(3, 6).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` end\n`; + // Add some interactions + diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[3]}0: Cross-group message\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render with different font settings', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.slice(0, 3).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Regular message\n`; + diagramCode += ` Note right of ${participantTypes[1]}1: Regular note\n`; + imgSnapshotTest(diagramCode, { + look, + sequence: { + actorFontFamily: 'courier', + actorFontSize: 14, + messageFontFamily: 'Arial', + messageFontSize: 12, + noteFontFamily: 'times', + noteFontSize: 16, + noteAlign: 'left', + }, + }); + }); + }); +}); + +// Additional tests for specific combinations +describe('Sequence Diagram Special Cases', () => { + it('should render complex sequence with all features', () => { + const diagramCode = ` + sequenceDiagram + box rgb(200,220,255) Authentication + actor User + boundary LoginUI + control AuthService + database UserDB + end + + box rgb(200,255,220) Order Processing + entity Order + queue OrderQueue + collections AuditLogs + end + + User ->> LoginUI: Enter credentials + LoginUI ->> AuthService: Validate + AuthService ->> UserDB: Query user + UserDB -->> AuthService: User data + alt Valid credentials + AuthService -->> LoginUI: Success + LoginUI -->> User: Welcome + + par Place order + User ->> Order: New order + Order ->> OrderQueue: Process + and + Order ->> AuditLogs: Record + end + + loop Until confirmed + OrderQueue ->> Order: Update status + Order -->> User: Notification + end + else Invalid credentials + AuthService --x LoginUI: Failure + LoginUI --x User: Retry + end + `; + imgSnapshotTest(diagramCode, {}); + }); + + it('should render with wrapped messages and notes', () => { + const diagramCode = ` + sequenceDiagram + participant A + participant B + + A ->> B: This is a very long message that should wrap properly in the diagram rendering + Note over A,B: This is a very long note that should also wrap properly when rendered in the diagram + + par Wrapped parallel + A ->> B: Parallel message 1
with explicit line break + and + B ->> A: Parallel message 2
with explicit line break + end + + loop Wrapped loop + Note right of B: This is a long note
in a loop + A ->> B: Message in loop + end + `; + imgSnapshotTest(diagramCode, { sequence: { wrap: true } }); + }); + describe('Sequence Diagram Rendering with Different Participant Types', () => { + it('should render a sequence diagram with various participant types', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor User + participant AuthService as Authentication Service + boundary UI + control OrderController + entity Product + database MongoDB + collections Products + queue OrderQueue + User ->> UI: Login request + UI ->> AuthService: Validate credentials + AuthService -->> UI: Authentication token + UI ->> OrderController: Place order + OrderController ->> Product: Check availability + Product -->> OrderController: Available + OrderController ->> MongoDB: Save order + MongoDB -->> OrderController: Order saved + OrderController ->> OrderQueue: Process payment + OrderQueue -->> User: Order confirmation + ` + ); + }); + + it('should render participant creation and destruction with different types', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Customer + participant Frontend + boundary PaymentGateway + Customer ->> Frontend: Place order + Frontend ->> OrderProcessor: Process order + create database OrderDB + OrderProcessor ->> OrderDB: Save order + ` + ); + }); + + it('should handle complex interactions between different participant types', () => { + imgSnapshotTest( + ` + sequenceDiagram + box rgba(200,220,255,0.5) System Components + actor User + boundary WebUI + control API + entity BusinessLogic + database MainDB + end + box rgba(200,255,220,0.5) External Services + queue MessageQueue + database AuditLogs + end + + User ->> WebUI: Submit request + WebUI ->> API: Process request + API ->> BusinessLogic: Execute business rules + BusinessLogic ->> MainDB: Query data + MainDB -->> BusinessLogic: Return results + BusinessLogic ->> MessageQueue: Publish event + MessageQueue -->> AuditLogs: Store audit trail + AuditLogs -->> API: Audit complete + API -->> WebUI: Return response + WebUI -->> User: Show results + `, + { sequence: { useMaxWidth: false } } + ); + }); + + it('should render parallel processes with different participant types', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Customer + participant Frontend + boundary PaymentService + control InventoryManager + entity Order + database OrdersDB + queue NotificationQueue + + Customer ->> Frontend: Place order + Frontend ->> Order: Create order + par Parallel Processing + Order ->> PaymentService: Process payment + and + Order ->> InventoryManager: Reserve items + end + PaymentService -->> Order: Payment confirmed + InventoryManager -->> Order: Items reserved + Order ->> OrdersDB: Save finalized order + OrdersDB -->> Order: Order saved + Order ->> NotificationQueue: Send confirmation + NotificationQueue -->> Customer: Order confirmation + ` + ); + }); + + it('should render different participant types with notes and loops', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Admin + participant Dashboard + boundary AuthService + control UserManager + entity UserProfile + database UserDB + database Logs + + Admin ->> Dashboard: Open user management + loop Authentication check + Dashboard ->> AuthService: Verify admin rights + AuthService ->> Dashboard: Access granted + end + Dashboard ->> UserManager: List users + UserManager ->> UserDB: Query users + UserDB ->> UserManager: Return user data + Note right of UserDB: Encrypted data
requires decryption + UserManager ->> UserProfile: Format profiles + UserProfile ->> UserManager: Formatted data + UserManager ->> Dashboard: Display users + Dashboard ->> Logs: Record access + Logs ->> Admin: Audit trail + ` + ); + }); + + it('should render different participant types with alternative flows', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Client + participant MobileApp + boundary CloudService + control DataProcessor + entity Transaction + database TransactionsDB + queue EventBus + + Client ->> MobileApp: Initiate transaction + MobileApp ->> CloudService: Authenticate + alt Authentication successful + CloudService -->> MobileApp: Auth token + MobileApp ->> DataProcessor: Process data + DataProcessor ->> Transaction: Create transaction + Transaction ->> TransactionsDB: Save record + TransactionsDB -->> Transaction: Confirmation + Transaction ->> EventBus: Publish event + EventBus -->> Client: Notification + else Authentication failed + CloudService -->> MobileApp: Error + MobileApp -->> Client: Show error + end + ` + ); + }); + + it('should render different participant types with wrapping text', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor LongNameUser as User With A Very
Long Name + participant FE as Frontend Service
With Long Name + boundary B as Boundary With
Multiline Name + control C as Control With
Multiline Name + entity E as Entity With
Multiline Name + database DB as Database With
Multiline Name + collections COL as Collections With
Multiline Name + queue Q as Queue With
Multiline Name + + LongNameUser ->> FE: This is a very long message that should wrap properly in the diagram + FE ->> B: Another long message
with explicit
line breaks + B -->> FE: Response message that is also quite long and needs to wrap + FE ->> C: Process data + C ->> E: Validate + E -->> C: Validation result + C ->> DB: Save + DB -->> C: Save result + C ->> COL: Log + COL -->> Q: Forward + Q -->> LongNameUser: Final response with confirmation of all actions taken + `, + { sequence: { wrap: true } } + ); + }); + describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => { + it('should render long notes left of boundary', () => { + imgSnapshotTest( + ` + sequenceDiagram + boundary Alice + actor Bob + Alice->>Bob: Hola + Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long notes left of control', () => { + imgSnapshotTest( + ` + sequenceDiagram + control Alice + actor Bob + Alice->>Bob: Hola + Note left of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render long notes right of entity', () => { + imgSnapshotTest( + ` + sequenceDiagram + entity Alice + actor Bob + Alice->>Bob: Hola + Note right of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long notes right of database', () => { + imgSnapshotTest( + ` + sequenceDiagram + database Alice + actor Bob + Alice->>Bob: Hola + Note right of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render long notes over collections', () => { + imgSnapshotTest( + ` + sequenceDiagram + collections Alice + actor Bob + Alice->>Bob: Hola + Note over Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long notes over queue', () => { + imgSnapshotTest( + ` + sequenceDiagram + queue Alice + actor Bob + Alice->>Bob: Hola + Note over Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render notes over actor and boundary', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Alice + boundary Charlie + note over Alice: Some note + note over Charlie: Other note + `, + {} + ); + }); + + it('should render long messages from database to collections', () => { + imgSnapshotTest( + ` + sequenceDiagram + database Alice + collections Bob + Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long messages from control to entity', () => { + imgSnapshotTest( + ` + sequenceDiagram + control Alice + entity Bob + Alice->>Bob:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render long messages from queue to boundary', () => { + imgSnapshotTest( + ` + sequenceDiagram + queue Alice + boundary Bob + Alice->>Bob: I'm short + Bob->>Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + `, + {} + ); + }); + + it('should render wrapped long messages from actor to database', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Alice + database Bob + Alice->>Bob: I'm short + Bob->>Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + `, + {} + ); + }); + }); + }); + + describe('svg size', () => { + it('should render a sequence diagram when useMaxWidth is true (default)', () => { + renderGraph( + ` + sequenceDiagram + actor Alice + boundary Bob + control John as John
Second Line + Alice ->> Bob: Hello Bob, how are you? + Bob-->>John: How about you John? + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row. + Bob-->Alice: Checking with John... + alt either this + Alice->>John: Yes + else or this + Alice->>John: No + else or this will happen + Alice->John: Maybe + end + par this happens in parallel + Alice -->> Bob: Parallel message 1 + and + Alice -->> John: Parallel message 2 + end + `, + { sequence: { useMaxWidth: true } } + ); + cy.get('svg').should((svg) => { + expect(svg).to.have.attr('width', '100%'); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + expect(maxWidthValue).to.be.within(820 * 0.95, 820 * 1.05); + }); + }); + + it('should render a sequence diagram when useMaxWidth is false', () => { + renderGraph( + ` + sequenceDiagram + actor Alice + boundary Bob + control John as John
Second Line + Alice ->> Bob: Hello Bob, how are you? + Bob-->>John: How about you John? + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row. + Bob-->Alice: Checking with John... + alt either this + Alice->>John: Yes + else or this + Alice->>John: No + else or this will happen + Alice->John: Maybe + end + par this happens in parallel + Alice -->> Bob: Parallel message 1 + and + Alice -->> John: Parallel message 2 + end + `, + { sequence: { useMaxWidth: false } } + ); + cy.get('svg').should((svg) => { + const width = parseFloat(svg.attr('width')); + expect(width).to.be.within(820 * 0.95, 820 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); + }); +}); From a4a27611dd94ecbb6ebb85ee49fd12c83984596e Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 1 Jul 2025 18:11:41 +0530 Subject: [PATCH 04/46] fix: minor refinement --- .../src/diagrams/sequence/sequenceRenderer.ts | 12 ++++++++---- packages/mermaid/src/diagrams/sequence/svgDraw.js | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index e44431928..d390ebada 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -1076,10 +1076,11 @@ export const draw = async function (_text: string, id: string, _version: string, for (const box of bounds.models.boxes) { box.height = bounds.getVerticalPos() - box.y; bounds.insert(box.x, box.y, box.x + box.width, box.height); - box.startx = box.x; - box.starty = box.y; - box.stopx = box.startx + box.width; - box.stopy = box.starty + box.height; + const boxPadding = conf.boxMargin * 2; + box.startx = box.x - boxPadding; + box.starty = box.y - boxPadding * 0.25; + box.stopx = box.startx + box.width + 2 * boxPadding; + box.stopy = box.starty + box.height + boxPadding * 0.75; box.stroke = 'rgb(0,0,0, 0.5)'; svgDraw.drawBox(diagram, box, conf); } @@ -1344,6 +1345,9 @@ async function calculateActorMargins( return (total += actors.get(aKey).width + (actors.get(aKey).margin || 0)); }, 0); + const standardBoxPadding = conf.boxMargin * 8; + totalWidth += standardBoxPadding; + totalWidth -= 2 * conf.boxTextMargin; if (box.wrap) { box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont); diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index 5304f57f5..6b8fa0208 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -712,13 +712,13 @@ const drawActorTypeControl = function (elem, actor, conf, isFooter) { .attr('marker-end', 'url(#filled-head-control)'); const bounds = actElem.node().getBBox(); - actor.height = bounds.height + conf.boxTextMargin; + actor.height = bounds.height + 2 * (conf?.sequence?.labelBoxHeight ?? 0); _drawTextCandidateFunc(conf, hasKatex(actor.description))( actor.description, actElem, rect.x, - rect.y + (!isFooter ? 30 : 40), + rect.y + 30, rect.width, rect.height, { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, @@ -774,8 +774,8 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { .attr('stroke', '#333') .attr('stroke-width', 2); - const boundBox = actElem.node().getBBox(); - actor.height = boundBox.height + conf.boxTextMargin; + const bounds = actElem.node().getBBox(); + actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); if (!isFooter) { actorCnt++; @@ -916,7 +916,7 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { const lastPath = cylinderGroup.select('path:last-child'); if (lastPath.node()) { const bounds = lastPath.node().getBBox(); - actor.height = bounds.height + 2 * conf.boxTextMargin; + actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); } return actor.height; @@ -989,7 +989,7 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { .attr('width', actor.width); const bounds = actElem.node().getBBox(); - actor.height = bounds.height + conf.boxTextMargin; + actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); _drawTextCandidateFunc(conf, hasKatex(actor.description))( actor.description, From 012530e98e9b8b80962ab270b6bb3b6d9f6ada05 Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 1 Jul 2025 19:02:57 +0530 Subject: [PATCH 05/46] added changeset --- .changeset/hungry-baths-glow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hungry-baths-glow.md diff --git a/.changeset/hungry-baths-glow.md b/.changeset/hungry-baths-glow.md new file mode 100644 index 000000000..b3084bcab --- /dev/null +++ b/.changeset/hungry-baths-glow.md @@ -0,0 +1,5 @@ +--- +'mermaid': minor +--- + +feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`. From ee82694645080e55ff5f8b42e1d84ec0cdf7d03d Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 8 Jul 2025 13:47:41 +0530 Subject: [PATCH 06/46] refactor(lexer): added contextual handling for new participant types as actors --- .../sequence/parser/sequenceDiagram.jison | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 4b5a82851..6cd0537cb 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -12,6 +12,25 @@ %options case-insensitive +%{ +function matchAsActorOrParticipant(tokenName, tokenType) { + const ahead = this._input; + + // Detect if an arrow or colon is coming right after the token + const arrowLike = /^(?:\s)*(->>|-->>|->|-->|<<->>|<<-->>)/; + const colonLike = /^\s*:/; + + // Treat as ACTOR if database appears inline in a message (arrow or colon follows) + if (arrowLike.test(ahead) || colonLike.test(ahead)) { + yytext = tokenName; + return 'ACTOR'; + } + // Otherwise treat as a participant type declaration + this.begin('ID'); + return tokenType; +} +%} + // Special states for recognizing aliases // A special state for grabbing text up to the first comment/newline %x ID ALIAS LINE @@ -31,12 +50,12 @@ "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } "actor" { this.begin('ID'); return 'participant_actor'; } -"boundary" { this.begin('ID'); return 'participant_boundary'; } -"control" { this.begin('ID'); return 'participant_control'; } -"entity" { this.begin('ID'); return 'participant_entity'; } -"database" { this.begin('ID'); return 'participant_database'; } -"collections" { this.begin('ID'); return 'participant_collections'; } -"queue" { this.begin('ID'); return 'participant_queue'; } +"boundary" { return matchAsActorOrParticipant.call(this, 'boundary', 'participant_boundary'); } +"control" { return matchAsActorOrParticipant.call(this, 'control', 'participant_control'); } +"entity" { return matchAsActorOrParticipant.call(this, 'entity', 'participant_entity'); } +"database" { return matchAsActorOrParticipant.call(this, 'database', 'participant_database'); } +"collections" { return matchAsActorOrParticipant.call(this, 'collections', 'participant_collections'); } +"queue" { return matchAsActorOrParticipant.call(this, 'queue', 'participant_queue'); } "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'; } From 3abcfbb8d287b45ef71d46590f2bbb8cc8bd9529 Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 8 Jul 2025 13:48:27 +0530 Subject: [PATCH 07/46] added unit tests --- .../diagrams/sequence/sequenceDiagram.spec.js | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index c3b8c2b4a..7bd278c23 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2038,4 +2038,189 @@ Bob->>Alice:Got it! expect(messages[0].from).toBe('Alice'); expect(messages[0].to).toBe('Bob'); }); + describe('when newly parsing messages ', () => { + it('should parse a message', async () => { + const actor1 = 'database'; + const diagram = await Diagram.fromText(` + sequenceDiagram + database Alice + database Bob + Bob->>+Alice: Hi Alice + Alice->>+Bob: Hi Bob + `); + + const messages = diagram.db.getMessages(); + }); + + it('should parse a message', async () => { + const actor1 = 'database'; + const diagram = await Diagram.fromText(` + sequenceDiagram + participant lead + queue dsa + API->>+Database: getUserb + Database-->>-API: userb + queue --> Database: hello + `); + + const messages = diagram.db.getMessages(); + }); + }); + describe('participant type parsing', () => { + it('should parse boundary participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + boundary B as Boundary Box + B->B: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('B').type).toBe('boundary'); + expect(actors.get('B').description).toBe('Boundary Box'); + }); + + it('should parse control participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + control C as Controller + C->C: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('C').type).toBe('control'); + expect(actors.get('C').description).toBe('Controller'); + }); + + it('should parse entity participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + entity E as Entity + E->E: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('E').type).toBe('entity'); + expect(actors.get('E').description).toBe('Entity'); + }); + + it('should parse database participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + database D as Database + D->D: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('D').type).toBe('database'); + expect(actors.get('D').description).toBe('Database'); + }); + + it('should parse collections participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + collections L as List + L->L: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('L').type).toBe('collections'); + expect(actors.get('L').description).toBe('List'); + }); + + it('should parse queue participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + queue Q as Jobs + Q->Q: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('Q').type).toBe('queue'); + expect(actors.get('Q').description).toBe('Jobs'); + }); + }); + + describe('participant type parsing', () => { + it('should parse actor participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + queue A as ActorName + A->A: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('A').type).toBe('queue'); + expect(actors.get('A').description).toBe('ActorName'); + }); + + it('should parse participant participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + database P as PartName + P->P: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('P').type).toBe('database'); + expect(actors.get('P').description).toBe('PartName'); + }); + + it('should parse boundary using actor keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + collections B as Boundary + B->B: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('B').type).toBe('collections'); + expect(actors.get('B').description).toBe('Boundary'); + }); + + it('should parse control using participant keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + control C as Controller + C->C: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('C').type).toBe('control'); + expect(actors.get('C').description).toBe('Controller'); + }); + + it('should parse entity using actor keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + entity E as Entity + E->E: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('E').type).toBe('entity'); + expect(actors.get('E').description).toBe('Entity'); + }); + + it('should parse database using participant keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + participant D as Database + D->D: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('D').type).toBe('participant'); + expect(actors.get('D').description).toBe('Database'); + }); + + it('should parse collections using actor keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + actor L as List + L->L: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('L').type).toBe('actor'); + expect(actors.get('L').description).toBe('List'); + }); + + it('should parse queue using participant keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + participant Q as Jobs + Q->Q: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('Q').type).toBe('participant'); + expect(actors.get('Q').description).toBe('Jobs'); + }); + }); }); From 237d01d510f679bbce6059be2974be6dcde60790 Mon Sep 17 00:00:00 2001 From: omkarht Date: Mon, 28 Jul 2025 16:47:54 +0530 Subject: [PATCH 08/46] fix: shifted matchAsActorOrParticipant function to sequenceDB file from sequenceDiagram.jison file on-behalf-of: @Mermaid-Chart --- .../sequence/parser/sequenceDiagram.jison | 30 ++++--------------- .../src/diagrams/sequence/sequenceDb.ts | 18 +++++++++++ .../src/diagrams/sequence/sequenceDiagram.ts | 8 ++++- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 6cd0537cb..359141b92 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -12,24 +12,6 @@ %options case-insensitive -%{ -function matchAsActorOrParticipant(tokenName, tokenType) { - const ahead = this._input; - - // Detect if an arrow or colon is coming right after the token - const arrowLike = /^(?:\s)*(->>|-->>|->|-->|<<->>|<<-->>)/; - const colonLike = /^\s*:/; - - // Treat as ACTOR if database appears inline in a message (arrow or colon follows) - if (arrowLike.test(ahead) || colonLike.test(ahead)) { - yytext = tokenName; - return 'ACTOR'; - } - // Otherwise treat as a participant type declaration - this.begin('ID'); - return tokenType; -} -%} // Special states for recognizing aliases // A special state for grabbing text up to the first comment/newline @@ -50,12 +32,12 @@ function matchAsActorOrParticipant(tokenName, tokenType) { "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } "actor" { this.begin('ID'); return 'participant_actor'; } -"boundary" { return matchAsActorOrParticipant.call(this, 'boundary', 'participant_boundary'); } -"control" { return matchAsActorOrParticipant.call(this, 'control', 'participant_control'); } -"entity" { return matchAsActorOrParticipant.call(this, 'entity', 'participant_entity'); } -"database" { return matchAsActorOrParticipant.call(this, 'database', 'participant_database'); } -"collections" { return matchAsActorOrParticipant.call(this, 'collections', 'participant_collections'); } -"queue" { return matchAsActorOrParticipant.call(this, 'queue', 'participant_queue'); } +"boundary" { return yy.matchAsActorOrParticipant('boundary', 'participant_boundary', this._input, this); } +"control" { return yy.matchAsActorOrParticipant('control', 'participant_control', this._input, this); } +"entity" { return yy.matchAsActorOrParticipant('entity', 'participant_entity', this._input, this); } +"database" { return yy.matchAsActorOrParticipant('database', 'participant_database', this._input, this); } +"collections" { return yy.matchAsActorOrParticipant('collections', 'participant_collections', this._input, this); } +"queue" { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); } "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'; } diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index d8421b84d..8b600c354 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -107,6 +107,7 @@ export class SequenceDB implements DiagramDB { this.apply = this.apply.bind(this); this.parseBoxData = this.parseBoxData.bind(this); this.parseMessage = this.parseMessage.bind(this); + this.matchAsActorOrParticipant = this.matchAsActorOrParticipant.bind(this); this.clear(); @@ -341,6 +342,23 @@ export class SequenceDB implements DiagramDB { return message; } + public matchAsActorOrParticipant( + tokenName: string, + tokenType: string, + inputRemainder: string, + lexer: any + ): string { + log.info({ tokenName }); + const arrowLike = /^\s*(->>|-->>|->|-->|<<->>|<<-->>|-x|--x|-\))/; + const colonLike = /^\s*:/; + + if (arrowLike.test(inputRemainder) || colonLike.test(inputRemainder)) { + return 'ACTOR'; + } + lexer.begin('ID'); // used the passed lexer + return tokenType; + } + // We expect the box statement to be color first then description // The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled // We extract first segment as color, the rest of the line is considered as text diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts index f2b701712..f7ca4938c 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts @@ -7,10 +7,16 @@ import { setConfig } from '../../diagram-api/diagramAPI.js'; import renderer from './sequenceRenderer.js'; import type { MermaidConfig } from '../../config.type.js'; +const db = new SequenceDB(); +parser.yy = { + parseMessage: db.parseMessage.bind(db), + matchAsActorOrParticipant: db.matchAsActorOrParticipant.bind(db), +}; + export const diagram: DiagramDefinition = { parser, get db() { - return new SequenceDB(); + return db; }, renderer, styles, From 334fe87bc6c3df222f03538efac514caa472d747 Mon Sep 17 00:00:00 2001 From: omkarht Date: Mon, 28 Jul 2025 18:33:58 +0530 Subject: [PATCH 09/46] fix: fixed failing test cases on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts index f7ca4938c..f2b701712 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts @@ -7,16 +7,10 @@ import { setConfig } from '../../diagram-api/diagramAPI.js'; import renderer from './sequenceRenderer.js'; import type { MermaidConfig } from '../../config.type.js'; -const db = new SequenceDB(); -parser.yy = { - parseMessage: db.parseMessage.bind(db), - matchAsActorOrParticipant: db.matchAsActorOrParticipant.bind(db), -}; - export const diagram: DiagramDefinition = { parser, get db() { - return db; + return new SequenceDB(); }, renderer, styles, From d525acc05b455a7fbdff496a53e12466291bfcd2 Mon Sep 17 00:00:00 2001 From: omkarht Date: Wed, 6 Aug 2025 17:08:57 +0530 Subject: [PATCH 10/46] chore: Add documentation for the syntax of the newly added participant type on-behalf-of: @Mermaid-Chart --- docs/syntax/sequenceDiagram.md | 216 ++++++++++++++++++ .../src/docs/syntax/sequenceDiagram.md | 120 ++++++++++ 2 files changed, 336 insertions(+) diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md index 84240a0cd..b197f402b 100644 --- a/docs/syntax/sequenceDiagram.md +++ b/docs/syntax/sequenceDiagram.md @@ -74,6 +74,222 @@ sequenceDiagram Bob->>Alice: Hi Alice ``` +### Boundary + +If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below. + +```mermaid-example +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +```mermaid +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +```mermaid-example +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +```mermaid +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +### Control + +If you want to use the control symbol for a participant, use the `control` statement as shown below. + +```mermaid-example +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +```mermaid +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +```mermaid-example +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +```mermaid +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +### Entity + +If you want to use the entity symbol for a participant, use the `entity` statement as shown below. + +```mermaid-example +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +```mermaid +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +```mermaid-example +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +```mermaid +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +### Database + +If you want to use the database symbol for a participant, use the `database` statement as shown below. + +```mermaid-example +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +```mermaid +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +```mermaid-example +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +```mermaid +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +### Collections + +If you want to use the collections symbol for a participant, use the `collections` statement as shown below. + +```mermaid-example +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +```mermaid +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +```mermaid-example +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +```mermaid +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +### Queue + +If you want to use the queue symbol for a participant, use the `queue` statement as shown below. + +```mermaid-example +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + +```mermaid +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + +```mermaid-example +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + +```mermaid +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + ### Aliases The actor can have a convenient identifier and a descriptive label. diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index 3087eb743..63c6bfc8f 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -46,6 +46,126 @@ sequenceDiagram Bob->>Alice: Hi Alice ``` +### Boundary + +If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below. + +```mermaid-example +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +```mermaid +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +### Control + +If you want to use the control symbol for a participant, use the `control` statement as shown below. + +```mermaid-example +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +```mermaid +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +### Entity + +If you want to use the entity symbol for a participant, use the `entity` statement as shown below. + +```mermaid-example +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +```mermaid +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +### Database + +If you want to use the database symbol for a participant, use the `database` statement as shown below. + +```mermaid-example +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +```mermaid +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +### Collections + +If you want to use the collections symbol for a participant, use the `collections` statement as shown below. + +```mermaid-example +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +```mermaid +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +### Queue + +If you want to use the queue symbol for a participant, use the `queue` statement as shown below. + +```mermaid-example +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + +```mermaid +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + ### Aliases The actor can have a convenient identifier and a descriptive label. From 99dbeba40718daea7ae16a78d664ec2b87d6eb85 Mon Sep 17 00:00:00 2001 From: omkarht Date: Wed, 6 Aug 2025 17:10:55 +0530 Subject: [PATCH 11/46] fix: refactored code on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/sequence/sequenceDb.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index 8b600c354..dc8a645fd 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -348,7 +348,6 @@ export class SequenceDB implements DiagramDB { inputRemainder: string, lexer: any ): string { - log.info({ tokenName }); const arrowLike = /^\s*(->>|-->>|->|-->|<<->>|<<-->>|-x|--x|-\))/; const colonLike = /^\s*:/; From 327a5aa9fde535589f5c7e3ce0598527ca6abd0b Mon Sep 17 00:00:00 2001 From: omkarht Date: Thu, 7 Aug 2025 18:07:13 +0530 Subject: [PATCH 12/46] chore: added logs in sequenceDiagram.jison on-behalf-of: @Mermaid-Chart --- .../sequence/parser/sequenceDiagram.jison | 262 ++++++++++++------ 1 file changed, 179 insertions(+), 83 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 359141b92..5902f76ba 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -15,88 +15,99 @@ // Special states for recognizing aliases // A special state for grabbing text up to the first comment/newline -%x ID ALIAS LINE +%x ID ALIAS LINE CONFIG CONFIG_DATA %x acc_title %x acc_descr %x acc_descr_multiline %% -[\n]+ return 'NEWLINE'; -\s+ /* skip all whitespace */ -((?!\n)\s)+ /* skip same-line whitespace */ -\#[^\n]* /* skip comments */ -\%%(?!\{)[^\n]* /* skip comments */ -[^\}]\%\%[^\n]* /* skip comments */ -[0-9]+(?=[ \n]+) return 'NUM'; -"box" { this.begin('LINE'); return 'box'; } -"participant" { this.begin('ID'); return 'participant'; } -"actor" { this.begin('ID'); return 'participant_actor'; } +[\n]+ { console.log("NEWLINE"); return 'NEWLINE'; } +\s+ { console.log("WHITESPACE"); /* skip whitespace */ } +((?!\n)\s)+ { console.log("SAME-LINE-WHITESPACE"); /* skip same-line whitespace */ } +\#[^\n]* { console.log("COMMENT"); /* skip comments */ } +\%%(?!\{)[^\n]* { console.log("COMMENT"); /* skip comments */ } +[^\}]\%\%[^\n]* { console.log("COMMENT"); /* skip comments */ } +[0-9]+(?=[ \n]+) { console.log("NUM:", yytext); return 'NUM'; } +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG_OBJECT:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } +// Enhanced config handling rules +\@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } +[^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } +\} { console.log("CONFIG_END"); this.popState(); return 'CONFIG_END'; } +"box" { console.log("BOX"); this.begin('LINE'); return 'box'; } +"participant" { console.log("PARTICIPANT"); this.begin('ID'); return 'participant'; } +"actor" { console.log("ACTOR_TYPE_ACTOR"); this.begin('ID'); return 'participant_actor'; } "boundary" { return yy.matchAsActorOrParticipant('boundary', 'participant_boundary', this._input, this); } "control" { return yy.matchAsActorOrParticipant('control', 'participant_control', this._input, this); } "entity" { return yy.matchAsActorOrParticipant('entity', 'participant_entity', this._input, this); } "database" { return yy.matchAsActorOrParticipant('database', 'participant_database', this._input, this); } "collections" { return yy.matchAsActorOrParticipant('collections', 'participant_collections', this._input, this); } "queue" { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); } -"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'; } -"rect" { this.begin('LINE'); return 'rect'; } -"opt" { this.begin('LINE'); return 'opt'; } -"alt" { this.begin('LINE'); return 'alt'; } -"else" { this.begin('LINE'); return 'else'; } -"par" { this.begin('LINE'); return 'par'; } -"par_over" { this.begin('LINE'); return 'par_over'; } -"and" { this.begin('LINE'); return 'and'; } -"critical" { this.begin('LINE'); return 'critical'; } -"option" { this.begin('LINE'); return 'option'; } -"break" { this.begin('LINE'); return 'break'; } -(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; } -"end" return 'end'; -"left of" return 'left_of'; -"right of" return 'right_of'; -"links" return 'links'; -"link" return 'link'; -"properties" return 'properties'; -"details" return 'details'; -"over" return 'over'; -"note" return 'note'; -"activate" { this.begin('ID'); return 'activate'; } -"deactivate" { this.begin('ID'); return 'deactivate'; } -"title"\s[^#\n;]+ return 'title'; -"title:"\s[^#\n;]+ return 'legacy_title'; -accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } -(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } -accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } -(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } -accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} -[\}] { this.popState(); } -[^\}]* return "acc_descr_multiline_value"; -"sequenceDiagram" return 'SD'; -"autonumber" return 'autonumber'; -"off" return 'off'; -"," return ','; -";" return 'NEWLINE'; -[^\+\<->\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\<->\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; } -"->>" return 'SOLID_ARROW'; -"<<->>" return 'BIDIRECTIONAL_SOLID_ARROW'; -"-->>" return 'DOTTED_ARROW'; -"<<-->>" return 'BIDIRECTIONAL_DOTTED_ARROW'; -"->" return 'SOLID_OPEN_ARROW'; -"-->" return 'DOTTED_OPEN_ARROW'; -\-[x] return 'SOLID_CROSS'; -\-\-[x] return 'DOTTED_CROSS'; -\-[\)] return 'SOLID_POINT'; -\-\-[\)] return 'DOTTED_POINT'; -":"(?:(?:no)?wrap:)?[^#\n;]* return 'TXT'; -":" return 'TXT'; -"+" return '+'; -"-" return '-'; -<> return 'NEWLINE'; -. return 'INVALID'; +"create" { console.log("CREATE"); return 'create'; } +"destroy" { console.log("DESTROY"); this.begin('ID'); return 'destroy'; } +// Updated ID rules to handle @{...} config +// [^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } +[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { console.log("ACTOR:", yytext); yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } +"as" { console.log("AS"); this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } +(?:) { console.log("ALIAS_END"); this.popState(); this.popState(); return 'NEWLINE'; } +// // Enhanced config handling rules +// \@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } +// [^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } +// \} { console.log("CONFIG_END"); this.popState(); return 'CONFIG_END'; } +"loop" { console.log("LOOP"); this.begin('LINE'); return 'loop'; } +"rect" { console.log("RECT"); this.begin('LINE'); return 'rect'; } +"opt" { console.log("OPT"); this.begin('LINE'); return 'opt'; } +"alt" { console.log("ALT"); this.begin('LINE'); return 'alt'; } +"else" { console.log("ELSE"); this.begin('LINE'); return 'else'; } +"par" { console.log("PAR"); this.begin('LINE'); return 'par'; } +"par_over" { console.log("PAR_OVER"); this.begin('LINE'); return 'par_over'; } +"and" { console.log("AND"); this.begin('LINE'); return 'and'; } +"critical" { console.log("CRITICAL"); this.begin('LINE'); return 'critical'; } +"option" { console.log("OPTION"); this.begin('LINE'); return 'option'; } +"break" { console.log("BREAK"); this.begin('LINE'); return 'break'; } +(?:[:]?(?:no)?wrap:)?[^#\n;]* { console.log("REST_OF_LINE:", yytext); this.popState(); return 'restOfLine'; } +"end" { console.log("END"); return 'end'; } +"left of" { console.log("LEFT_OF"); return 'left_of'; } +"right of" { console.log("RIGHT_OF"); return 'right_of'; } +"links" { console.log("LINKS"); return 'links'; } +"link" { console.log("LINK"); return 'link'; } +"properties" { console.log("PROPERTIES"); return 'properties'; } +"details" { console.log("DETAILS"); return 'details'; } +"over" { console.log("OVER"); return 'over'; } +"note" { console.log("NOTE"); return 'note'; } +"activate" { console.log("ACTIVATE"); this.begin('ID'); return 'activate'; } +"deactivate" { console.log("DEACTIVATE"); this.begin('ID'); return 'deactivate'; } +"title"\s[^#\n;]+ { console.log("TITLE"); return 'title'; } +"title:"\s[^#\n;]+ { console.log("LEGACY_TITLE"); return 'legacy_title'; } +accTitle\s*":"\s* { console.log("ACC_TITLE"); this.begin("acc_title"); return 'acc_title'; } +(?!\n|;|#)*[^\n]* { console.log("ACC_TITLE_VALUE:", yytext); this.popState(); return "acc_title_value"; } +accDescr\s*":"\s* { console.log("ACC_DESCR"); this.begin("acc_descr"); return 'acc_descr'; } +(?!\n|;|#)*[^\n]* { console.log("ACC_DESCR_VALUE:", yytext); this.popState(); return "acc_descr_value"; } +accDescr\s*"{"\s* { console.log("ACC_DESCR_MULTILINE_START"); this.begin("acc_descr_multiline"); } +[\}] { console.log("ACC_DESCR_MULTILINE_END"); this.popState(); } +[^\}]* { console.log("ACC_DESCR_MULTILINE_VALUE:", yytext); return "acc_descr_multiline_value"; } +"sequenceDiagram" { console.log("SEQUENCE_DIAGRAM"); return 'SD'; } +"autonumber" { console.log("AUTONUMBER"); return 'autonumber'; } +"off" { console.log("OFF"); return 'off'; } +"," { console.log("COMMA"); return ','; } +";" { console.log("SEMICOLON"); return 'NEWLINE'; } +[^\+\<->\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\<->\->:\n,;]+)* { console.log("ACTOR_GENERIC:", yytext); yytext = yytext.trim(); return 'ACTOR'; } +"->>" { console.log("SOLID_ARROW"); return 'SOLID_ARROW'; } +"<<->>" { console.log("BIDIRECTIONAL_SOLID_ARROW"); return 'BIDIRECTIONAL_SOLID_ARROW'; } +"-->>" { console.log("DOTTED_ARROW"); return 'DOTTED_ARROW'; } +"<<-->>" { console.log("BIDIRECTIONAL_DOTTED_ARROW"); return 'BIDIRECTIONAL_DOTTED_ARROW'; } +"->" { console.log("SOLID_OPEN_ARROW"); return 'SOLID_OPEN_ARROW'; } +"-->" { console.log("DOTTED_OPEN_ARROW"); return 'DOTTED_OPEN_ARROW'; } +\-[x] { console.log("SOLID_CROSS"); return 'SOLID_CROSS'; } +\-\-[x] { console.log("DOTTED_CROSS"); return 'DOTTED_CROSS'; } +\-[\)] { console.log("SOLID_POINT"); return 'SOLID_POINT'; } +\-\-[\)] { console.log("DOTTED_POINT"); return 'DOTTED_POINT'; } +":"(?:(?:no)?wrap:)?[^#\n;]* { console.log("TEXT_WITH_WRAP:", yytext); return 'TXT'; } +":" { console.log("TEXT"); return 'TXT'; } +"+" { console.log("PLUS"); return '+'; } +"-" { console.log("MINUS"); return '-'; } +<> { console.log("EOF"); return 'NEWLINE'; } +. { console.log("INVALID:", yytext); return 'INVALID'; } /lex @@ -233,13 +244,40 @@ else_sections ; participant_statement - : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant' actor 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$$=$2;} - | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} - | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} - - | 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + : 'participant' actor_with_config 'AS' restOfLine 'NEWLINE' + { + console.log('Participant with config and alias:', $2, $4); + $2.draw = 'participant'; + $2.type = 'addParticipant'; + $2.description = yy.parseMessage($4); + $$ = $2; + } + | 'participant' actor_with_config 'NEWLINE' + { + console.log('Participant with config without alias:', $2); + $2.draw = 'participant'; + $2.type = 'addParticipant'; + $$ = $2; + } + | 'participant_actor' actor_with_config 'AS' restOfLine 'NEWLINE' + { + $2.draw = 'actor'; + $2.type = 'addParticipant'; + $2.description = yy.parseMessage($4); + $$ = $2; + } + | 'participant_actor' actor_with_config 'NEWLINE' + { + $2.draw = 'actor'; + $2.type = 'addParticipant'; + $$ = $2; + } + | 'destroy' actor 'NEWLINE' + { + $2.type = 'destroyParticipant'; + $$ = $2; + } + | 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | 'participant_boundary' actor 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant'; $$=$2;} | 'participant_control' actor 'AS' restOfLine 'NEWLINE' {$2.draw='control'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} @@ -327,13 +365,71 @@ signal { $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]} ; -// actor -// : actor_participant -// | actor_actor -// ; +actor_with_config + : ACTOR_WITH_CONFIG config_object // Changed from ACTOR + { + console.log("ACTOR_WITH_CONFIG Actor with config:", $1, $2); + $$ = { + type: 'addParticipant', + actor: $1, + config: $2 + }; + } + | ACTOR config_object + { + console.log("Actor with config:", $1, $2); + $$ = { + type: 'addParticipant', + actor: $1, + config: $2 + }; + } + | ACTOR + { + console.log("Actor without config:", $1); + $$ = { + type: 'addParticipant', + actor: $1, + }; + } + ; -actor: ACTOR {$$={ type: 'addParticipant', actor:$1}}; -// actor_actor: ACTOR {$$={type: 'addActor', actor:$1}}; +config_object + : CONFIG_START CONFIG_CONTENT CONFIG_END + { + console.log("Parsing config content:", $2); + try { + // Remove any trailing whitespace/newlines + const content = $2.trim(); + $$ = JSON.parse(content); + console.log("Successfully parsed JSON config:", $$); + } catch (e) { + console.log("JSON parse failed, using raw content"); + $$ = $2.trim(); + } + } + ; +actor + : ACTOR_WITH_CONFIG // Add this case + { + console.log("Actor with config flag:", $1); + $$ = { type: 'addParticipant', actor: $1 }; + } + | actor config_object + { + console.log("Actor with config:", $1, $2); + $$ = { + type: 'addParticipant', + actor: $1.actor, + config: $2 + }; + } + | ACTOR + { + console.log("Basic actor:", $1); + $$ = { type: 'addParticipant', actor: $1 }; + } + ; signaltype : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } From 981829a426f1b7ee182a263c9316dd46295075ee Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Thu, 7 Aug 2025 15:21:13 +0200 Subject: [PATCH 13/46] Updated order of lexer statements in the grammar --- .../diagrams/sequence/parser/sequenceDiagram.jison | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 5902f76ba..442bec590 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -29,11 +29,14 @@ \%%(?!\{)[^\n]* { console.log("COMMENT"); /* skip comments */ } [^\}]\%\%[^\n]* { console.log("COMMENT"); /* skip comments */ } [0-9]+(?=[ \n]+) { console.log("NUM:", yytext); return 'NUM'; } -[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG_OBJECT:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } -// Enhanced config handling rules +// Enhanced config handling rules - moved before other ID rules for proper precedence \@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } [^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } -\} { console.log("CONFIG_END"); this.popState(); return 'CONFIG_END'; } +\} { console.log("CONFIG_END"); this.popState(); this.popState(); return 'CONFIG_END'; } +// ACTOR_WITH_CONFIG rule must come before general ACTOR rule for proper precedence +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG_OBJECT:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } +// General ACTOR rule - now comes after the more specific ACTOR_WITH_CONFIG rule +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { console.log("ACTOR:", yytext); yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "box" { console.log("BOX"); this.begin('LINE'); return 'box'; } "participant" { console.log("PARTICIPANT"); this.begin('ID'); return 'participant'; } "actor" { console.log("ACTOR_TYPE_ACTOR"); this.begin('ID'); return 'participant_actor'; } @@ -45,9 +48,6 @@ "queue" { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); } "create" { console.log("CREATE"); return 'create'; } "destroy" { console.log("DESTROY"); this.begin('ID'); return 'destroy'; } -// Updated ID rules to handle @{...} config -// [^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } -[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { console.log("ACTOR:", yytext); yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "as" { console.log("AS"); this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } (?:) { console.log("ALIAS_END"); this.popState(); this.popState(); return 'NEWLINE'; } // // Enhanced config handling rules From 677ff82d1359cd85058575dd0241135bcbe39beb Mon Sep 17 00:00:00 2001 From: omkarht Date: Thu, 7 Aug 2025 20:45:23 +0530 Subject: [PATCH 14/46] chore: implemented new syntx on-behalf-of: @Mermaid-Chart --- .../sequence/parser/sequenceDiagram.jison | 277 ++++++------------ .../src/diagrams/sequence/sequenceDb.ts | 36 ++- .../diagrams/sequence/sequenceDiagram.spec.js | 95 ++---- packages/mermaid/src/types.ts | 12 + 4 files changed, 150 insertions(+), 270 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 442bec590..87ef8212f 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -12,7 +12,6 @@ %options case-insensitive - // Special states for recognizing aliases // A special state for grabbing text up to the first comment/newline %x ID ALIAS LINE CONFIG CONFIG_DATA @@ -22,92 +21,79 @@ %x acc_descr_multiline %% -[\n]+ { console.log("NEWLINE"); return 'NEWLINE'; } -\s+ { console.log("WHITESPACE"); /* skip whitespace */ } -((?!\n)\s)+ { console.log("SAME-LINE-WHITESPACE"); /* skip same-line whitespace */ } -\#[^\n]* { console.log("COMMENT"); /* skip comments */ } -\%%(?!\{)[^\n]* { console.log("COMMENT"); /* skip comments */ } -[^\}]\%\%[^\n]* { console.log("COMMENT"); /* skip comments */ } -[0-9]+(?=[ \n]+) { console.log("NUM:", yytext); return 'NUM'; } -// Enhanced config handling rules - moved before other ID rules for proper precedence -\@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } -[^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } -\} { console.log("CONFIG_END"); this.popState(); this.popState(); return 'CONFIG_END'; } -// ACTOR_WITH_CONFIG rule must come before general ACTOR rule for proper precedence -[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG_OBJECT:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } -// General ACTOR rule - now comes after the more specific ACTOR_WITH_CONFIG rule -[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { console.log("ACTOR:", yytext); yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } -"box" { console.log("BOX"); this.begin('LINE'); return 'box'; } -"participant" { console.log("PARTICIPANT"); this.begin('ID'); return 'participant'; } -"actor" { console.log("ACTOR_TYPE_ACTOR"); this.begin('ID'); return 'participant_actor'; } -"boundary" { return yy.matchAsActorOrParticipant('boundary', 'participant_boundary', this._input, this); } -"control" { return yy.matchAsActorOrParticipant('control', 'participant_control', this._input, this); } -"entity" { return yy.matchAsActorOrParticipant('entity', 'participant_entity', this._input, this); } -"database" { return yy.matchAsActorOrParticipant('database', 'participant_database', this._input, this); } -"collections" { return yy.matchAsActorOrParticipant('collections', 'participant_collections', this._input, this); } -"queue" { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); } -"create" { console.log("CREATE"); return 'create'; } -"destroy" { console.log("DESTROY"); this.begin('ID'); return 'destroy'; } -"as" { console.log("AS"); this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } -(?:) { console.log("ALIAS_END"); this.popState(); this.popState(); return 'NEWLINE'; } -// // Enhanced config handling rules -// \@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } -// [^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } -// \} { console.log("CONFIG_END"); this.popState(); return 'CONFIG_END'; } -"loop" { console.log("LOOP"); this.begin('LINE'); return 'loop'; } -"rect" { console.log("RECT"); this.begin('LINE'); return 'rect'; } -"opt" { console.log("OPT"); this.begin('LINE'); return 'opt'; } -"alt" { console.log("ALT"); this.begin('LINE'); return 'alt'; } -"else" { console.log("ELSE"); this.begin('LINE'); return 'else'; } -"par" { console.log("PAR"); this.begin('LINE'); return 'par'; } -"par_over" { console.log("PAR_OVER"); this.begin('LINE'); return 'par_over'; } -"and" { console.log("AND"); this.begin('LINE'); return 'and'; } -"critical" { console.log("CRITICAL"); this.begin('LINE'); return 'critical'; } -"option" { console.log("OPTION"); this.begin('LINE'); return 'option'; } -"break" { console.log("BREAK"); this.begin('LINE'); return 'break'; } -(?:[:]?(?:no)?wrap:)?[^#\n;]* { console.log("REST_OF_LINE:", yytext); this.popState(); return 'restOfLine'; } -"end" { console.log("END"); return 'end'; } -"left of" { console.log("LEFT_OF"); return 'left_of'; } -"right of" { console.log("RIGHT_OF"); return 'right_of'; } -"links" { console.log("LINKS"); return 'links'; } -"link" { console.log("LINK"); return 'link'; } -"properties" { console.log("PROPERTIES"); return 'properties'; } -"details" { console.log("DETAILS"); return 'details'; } -"over" { console.log("OVER"); return 'over'; } -"note" { console.log("NOTE"); return 'note'; } -"activate" { console.log("ACTIVATE"); this.begin('ID'); return 'activate'; } -"deactivate" { console.log("DEACTIVATE"); this.begin('ID'); return 'deactivate'; } -"title"\s[^#\n;]+ { console.log("TITLE"); return 'title'; } -"title:"\s[^#\n;]+ { console.log("LEGACY_TITLE"); return 'legacy_title'; } -accTitle\s*":"\s* { console.log("ACC_TITLE"); this.begin("acc_title"); return 'acc_title'; } -(?!\n|;|#)*[^\n]* { console.log("ACC_TITLE_VALUE:", yytext); this.popState(); return "acc_title_value"; } -accDescr\s*":"\s* { console.log("ACC_DESCR"); this.begin("acc_descr"); return 'acc_descr'; } -(?!\n|;|#)*[^\n]* { console.log("ACC_DESCR_VALUE:", yytext); this.popState(); return "acc_descr_value"; } -accDescr\s*"{"\s* { console.log("ACC_DESCR_MULTILINE_START"); this.begin("acc_descr_multiline"); } -[\}] { console.log("ACC_DESCR_MULTILINE_END"); this.popState(); } -[^\}]* { console.log("ACC_DESCR_MULTILINE_VALUE:", yytext); return "acc_descr_multiline_value"; } -"sequenceDiagram" { console.log("SEQUENCE_DIAGRAM"); return 'SD'; } -"autonumber" { console.log("AUTONUMBER"); return 'autonumber'; } -"off" { console.log("OFF"); return 'off'; } -"," { console.log("COMMA"); return ','; } -";" { console.log("SEMICOLON"); return 'NEWLINE'; } -[^\+\<->\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\<->\->:\n,;]+)* { console.log("ACTOR_GENERIC:", yytext); yytext = yytext.trim(); return 'ACTOR'; } -"->>" { console.log("SOLID_ARROW"); return 'SOLID_ARROW'; } -"<<->>" { console.log("BIDIRECTIONAL_SOLID_ARROW"); return 'BIDIRECTIONAL_SOLID_ARROW'; } -"-->>" { console.log("DOTTED_ARROW"); return 'DOTTED_ARROW'; } -"<<-->>" { console.log("BIDIRECTIONAL_DOTTED_ARROW"); return 'BIDIRECTIONAL_DOTTED_ARROW'; } -"->" { console.log("SOLID_OPEN_ARROW"); return 'SOLID_OPEN_ARROW'; } -"-->" { console.log("DOTTED_OPEN_ARROW"); return 'DOTTED_OPEN_ARROW'; } -\-[x] { console.log("SOLID_CROSS"); return 'SOLID_CROSS'; } -\-\-[x] { console.log("DOTTED_CROSS"); return 'DOTTED_CROSS'; } -\-[\)] { console.log("SOLID_POINT"); return 'SOLID_POINT'; } -\-\-[\)] { console.log("DOTTED_POINT"); return 'DOTTED_POINT'; } -":"(?:(?:no)?wrap:)?[^#\n;]* { console.log("TEXT_WITH_WRAP:", yytext); return 'TXT'; } -":" { console.log("TEXT"); return 'TXT'; } -"+" { console.log("PLUS"); return '+'; } -"-" { console.log("MINUS"); return '-'; } -<> { console.log("EOF"); return 'NEWLINE'; } -. { console.log("INVALID:", yytext); return 'INVALID'; } +[\n]+ return 'NEWLINE'; +\s+ /* skip all whitespace */ +((?!\n)\s)+ /* skip same-line whitespace */ +\#[^\n]* /* skip comments */ +\%%(?!\{)[^\n]* /* skip comments */ +[^\}]\%\%[^\n]* /* skip comments */ +[0-9]+(?=[ \n]+) return 'NUM'; +\@\{ { this.begin('CONFIG'); return 'CONFIG_START'; } +[^\}]+ { return 'CONFIG_CONTENT'; } +\} { this.popState(); this.popState(); return 'CONFIG_END'; } +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; } +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } +"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'; } +"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } +(?:) { this.popState(); this.popState(); return 'NEWLINE'; } +"loop" { this.begin('LINE'); return 'loop'; } +"rect" { this.begin('LINE'); return 'rect'; } +"opt" { this.begin('LINE'); return 'opt'; } +"alt" { this.begin('LINE'); return 'alt'; } +"else" { this.begin('LINE'); return 'else'; } +"par" { this.begin('LINE'); return 'par'; } +"par_over" { this.begin('LINE'); return 'par_over'; } +"and" { this.begin('LINE'); return 'and'; } +"critical" { this.begin('LINE'); return 'critical'; } +"option" { this.begin('LINE'); return 'option'; } +"break" { this.begin('LINE'); return 'break'; } +(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; } +"end" return 'end'; +"left of" return 'left_of'; +"right of" return 'right_of'; +"links" return 'links'; +"link" return 'link'; +"properties" return 'properties'; +"details" return 'details'; +"over" return 'over'; +"note" return 'note'; +"activate" { this.begin('ID'); return 'activate'; } +"deactivate" { this.begin('ID'); return 'deactivate'; } +"title"\s[^#\n;]+ return 'title'; +"title:"\s[^#\n;]+ return 'legacy_title'; +accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } +accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } +accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} +[\}] { this.popState(); } +[^\}]* return "acc_descr_multiline_value"; +"sequenceDiagram" return 'SD'; +"autonumber" return 'autonumber'; +"off" return 'off'; +"," return ','; +";" return 'NEWLINE'; +[^\+\<->\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\<->\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; } +"->>" return 'SOLID_ARROW'; +"<<->>" return 'BIDIRECTIONAL_SOLID_ARROW'; +"-->>" return 'DOTTED_ARROW'; +"<<-->>" return 'BIDIRECTIONAL_DOTTED_ARROW'; +"->" return 'SOLID_OPEN_ARROW'; +"-->" return 'DOTTED_OPEN_ARROW'; +\-[x] return 'SOLID_CROSS'; +\-\-[x] return 'DOTTED_CROSS'; +\-[\)] return 'SOLID_POINT'; +\-\-[\)] return 'DOTTED_POINT'; +":"(?:(?:no)?wrap:)?[^#\n;]* return 'TXT'; +":" return 'TXT'; +"+" return '+'; +"-" return '-'; +<> return 'NEWLINE'; +. return 'INVALID'; /lex @@ -244,56 +230,12 @@ else_sections ; participant_statement - : 'participant' actor_with_config 'AS' restOfLine 'NEWLINE' - { - console.log('Participant with config and alias:', $2, $4); - $2.draw = 'participant'; - $2.type = 'addParticipant'; - $2.description = yy.parseMessage($4); - $$ = $2; - } - | 'participant' actor_with_config 'NEWLINE' - { - console.log('Participant with config without alias:', $2); - $2.draw = 'participant'; - $2.type = 'addParticipant'; - $$ = $2; - } - | 'participant_actor' actor_with_config 'AS' restOfLine 'NEWLINE' - { - $2.draw = 'actor'; - $2.type = 'addParticipant'; - $2.description = yy.parseMessage($4); - $$ = $2; - } - | 'participant_actor' actor_with_config 'NEWLINE' - { - $2.draw = 'actor'; - $2.type = 'addParticipant'; - $$ = $2; - } - | 'destroy' actor 'NEWLINE' - { - $2.type = 'destroyParticipant'; - $$ = $2; - } - | 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_boundary' actor 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant'; $$=$2;} - - | 'participant_control' actor 'AS' restOfLine 'NEWLINE' {$2.draw='control'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_control' actor 'NEWLINE' {$2.draw='control'; $2.type='addParticipant'; $$=$2;} - - | 'participant_entity' actor 'AS' restOfLine 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_entity' actor 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant'; $$=$2;} - - | 'participant_database' actor 'AS' restOfLine 'NEWLINE' {$2.draw='database'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_database' actor 'NEWLINE' {$2.draw='database'; $2.type='addParticipant'; $$=$2;} - - | 'participant_collections' actor 'AS' restOfLine 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_collections' actor 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant'; $$=$2;} - - | 'participant_queue' actor 'AS' restOfLine 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_queue' actor 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant'; $$=$2;} + : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant' actor 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$$=$2;} + | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} + | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} + | 'participant' actor_with_config 'NEWLINE' { $2.draw = 'participant'; $2.type = 'addParticipant'; $$ = $2;} ; @@ -366,76 +308,35 @@ signal ; actor_with_config - : ACTOR_WITH_CONFIG config_object // Changed from ACTOR + : ACTOR config_object { - console.log("ACTOR_WITH_CONFIG Actor with config:", $1, $2); $$ = { type: 'addParticipant', actor: $1, config: $2 }; } - | ACTOR config_object - { - console.log("Actor with config:", $1, $2); - $$ = { - type: 'addParticipant', - actor: $1, - config: $2 - }; - } - | ACTOR - { - console.log("Actor without config:", $1); - $$ = { - type: 'addParticipant', - actor: $1, - }; - } ; config_object : CONFIG_START CONFIG_CONTENT CONFIG_END { - console.log("Parsing config content:", $2); - try { - // Remove any trailing whitespace/newlines - const content = $2.trim(); - $$ = JSON.parse(content); - console.log("Successfully parsed JSON config:", $$); - } catch (e) { - console.log("JSON parse failed, using raw content"); - $$ = $2.trim(); - } - } - ; -actor - : ACTOR_WITH_CONFIG // Add this case - { - console.log("Actor with config flag:", $1); - $$ = { type: 'addParticipant', actor: $1 }; - } - | actor config_object - { - console.log("Actor with config:", $1, $2); - $$ = { - type: 'addParticipant', - actor: $1.actor, - config: $2 - }; - } - | ACTOR - { - console.log("Basic actor:", $1); - $$ = { type: 'addParticipant', actor: $1 }; + $$ = $2.trim(); } ; +// actor +// : actor_participant +// | actor_actor +// ; + +actor: ACTOR {$$={ type: 'addParticipant', actor:$1}}; +// actor_actor: ACTOR {$$={type: 'addActor', actor:$1}}; signaltype : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } | DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; } | SOLID_ARROW { $$ = yy.LINETYPE.SOLID; } - | BIDIRECTIONAL_SOLID_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_SOLID; } + | BIDIRECTIONAL_SOLID_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_SOLID; } | DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; } | BIDIRECTIONAL_DOTTED_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_DOTTED; } | SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; } diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index dc8a645fd..4f06a1ebe 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -1,4 +1,5 @@ import { getConfig } from '../../diagram-api/diagramAPI.js'; +import * as yaml from 'js-yaml'; import type { DiagramDB } from '../../diagram-api/types.js'; import { log } from '../../logger.js'; import { ImperativeState } from '../../utils/imperativeState.js'; @@ -13,6 +14,7 @@ import { setDiagramTitle, } from '../common/commonDb.js'; import type { Actor, AddMessageParams, Box, Message, Note } from './types.js'; +import type { ParticipantMetaData } from '../../types.js'; interface SequenceState { prevActor?: string; @@ -107,7 +109,6 @@ export class SequenceDB implements DiagramDB { this.apply = this.apply.bind(this); this.parseBoxData = this.parseBoxData.bind(this); this.parseMessage = this.parseMessage.bind(this); - this.matchAsActorOrParticipant = this.matchAsActorOrParticipant.bind(this); this.clear(); @@ -131,9 +132,22 @@ export class SequenceDB implements DiagramDB { id: string, name: string, description: { text: string; wrap?: boolean | null; type: string }, - type: string + type: string, + metadata?: any ) { let assignedBox = this.state.records.currentBox; + 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 ParticipantMetaData; + } + type = doc?.type ?? type; const old = this.state.records.actors.get(id); if (old) { // If already set and trying to set to a new one throw error @@ -342,22 +356,6 @@ export class SequenceDB implements DiagramDB { return message; } - public matchAsActorOrParticipant( - tokenName: string, - tokenType: string, - inputRemainder: string, - lexer: any - ): string { - const arrowLike = /^\s*(->>|-->>|->|-->|<<->>|<<-->>|-x|--x|-\))/; - const colonLike = /^\s*:/; - - if (arrowLike.test(inputRemainder) || colonLike.test(inputRemainder)) { - return 'ACTOR'; - } - lexer.begin('ID'); // used the passed lexer - return tokenType; - } - // We expect the box statement to be color first then description // The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled // We extract first segment as color, the rest of the line is considered as text @@ -546,7 +544,7 @@ export class SequenceDB implements DiagramDB { }); break; case 'addParticipant': - this.addActor(param.actor, param.actor, param.description, param.draw); + this.addActor(param.actor, param.actor, param.description, param.draw, param.config); break; case 'createParticipant': if (this.state.records.actors.has(param.actor)) { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 7bd278c23..8088f3c44 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2043,8 +2043,8 @@ Bob->>Alice:Got it! const actor1 = 'database'; const diagram = await Diagram.fromText(` sequenceDiagram - database Alice - database Bob + participant Alice@{ "type" : "database" } + participant Bob@{ "type" : "database" } Bob->>+Alice: Hi Alice Alice->>+Bob: Hi Bob `); @@ -2057,10 +2057,10 @@ Bob->>Alice:Got it! const diagram = await Diagram.fromText(` sequenceDiagram participant lead - queue dsa + participant dsa@{ "type" : "queue" } API->>+Database: getUserb Database-->>-API: userb - queue --> Database: hello + dsa --> Database: hello `); const messages = diagram.db.getMessages(); @@ -2070,67 +2070,67 @@ Bob->>Alice:Got it! it('should parse boundary participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - boundary B as Boundary Box - B->B: test + participant boundary@{ "type" : "boundary" } + boundary->boundary: test `); const actors = diagram.db.getActors(); - expect(actors.get('B').type).toBe('boundary'); - expect(actors.get('B').description).toBe('Boundary Box'); + expect(actors.get('boundary').type).toBe('boundary'); + expect(actors.get('boundary').description).toBe('boundary'); }); it('should parse control participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - control C as Controller + participant C@{ "type" : "control" } C->C: test `); const actors = diagram.db.getActors(); expect(actors.get('C').type).toBe('control'); - expect(actors.get('C').description).toBe('Controller'); + expect(actors.get('C').description).toBe('C'); }); it('should parse entity participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - entity E as Entity + participant E@{ "type" : "entity" } E->E: test `); const actors = diagram.db.getActors(); expect(actors.get('E').type).toBe('entity'); - expect(actors.get('E').description).toBe('Entity'); + expect(actors.get('E').description).toBe('E'); }); it('should parse database participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - database D as Database + participant D@{ "type" : "database" } D->D: test `); const actors = diagram.db.getActors(); expect(actors.get('D').type).toBe('database'); - expect(actors.get('D').description).toBe('Database'); + expect(actors.get('D').description).toBe('D'); }); it('should parse collections participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - collections L as List + participant L@{ "type" : "collections" } L->L: test `); const actors = diagram.db.getActors(); expect(actors.get('L').type).toBe('collections'); - expect(actors.get('L').description).toBe('List'); + expect(actors.get('L').description).toBe('L'); }); it('should parse queue participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - queue Q as Jobs + participant Q@{ "type" : "queue" } Q->Q: test `); const actors = diagram.db.getActors(); expect(actors.get('Q').type).toBe('queue'); - expect(actors.get('Q').description).toBe('Jobs'); + expect(actors.get('Q').description).toBe('Q'); }); }); @@ -2138,89 +2138,58 @@ Bob->>Alice:Got it! it('should parse actor participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - queue A as ActorName + participant A@{ "type" : "queue" } A->A: test `); const actors = diagram.db.getActors(); expect(actors.get('A').type).toBe('queue'); - expect(actors.get('A').description).toBe('ActorName'); + expect(actors.get('A').description).toBe('A'); }); it('should parse participant participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - database P as PartName + participant P@{ "type" : "database" } P->P: test `); const actors = diagram.db.getActors(); expect(actors.get('P').type).toBe('database'); - expect(actors.get('P').description).toBe('PartName'); + expect(actors.get('P').description).toBe('P'); }); it('should parse boundary using actor keyword', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - collections B as Boundary - B->B: test + participant Alice@{ "type" : "collections" } + participant Bob@{ "type" : "control" } + Alice->>Bob: Hello Bob, how are you? `); const actors = diagram.db.getActors(); - expect(actors.get('B').type).toBe('collections'); - expect(actors.get('B').description).toBe('Boundary'); + expect(actors.get('Alice').type).toBe('collections'); + expect(actors.get('Bob').type).toBe('control'); + expect(actors.get('Bob').description).toBe('Bob'); }); it('should parse control using participant keyword', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - control C as Controller + participant C@{ "type" : "control" } C->C: test `); const actors = diagram.db.getActors(); expect(actors.get('C').type).toBe('control'); - expect(actors.get('C').description).toBe('Controller'); + expect(actors.get('C').description).toBe('C'); }); it('should parse entity using actor keyword', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - entity E as Entity + participant E@{ "type" : "entity" } E->E: test `); const actors = diagram.db.getActors(); expect(actors.get('E').type).toBe('entity'); - expect(actors.get('E').description).toBe('Entity'); - }); - - it('should parse database using participant keyword', async () => { - const diagram = await Diagram.fromText(` - sequenceDiagram - participant D as Database - D->D: test - `); - const actors = diagram.db.getActors(); - expect(actors.get('D').type).toBe('participant'); - expect(actors.get('D').description).toBe('Database'); - }); - - it('should parse collections using actor keyword', async () => { - const diagram = await Diagram.fromText(` - sequenceDiagram - actor L as List - L->L: test - `); - const actors = diagram.db.getActors(); - expect(actors.get('L').type).toBe('actor'); - expect(actors.get('L').description).toBe('List'); - }); - - it('should parse queue using participant keyword', async () => { - const diagram = await Diagram.fromText(` - sequenceDiagram - participant Q as Jobs - Q->Q: test - `); - const actors = diagram.db.getActors(); - expect(actors.get('Q').type).toBe('participant'); - expect(actors.get('Q').description).toBe('Jobs'); + expect(actors.get('E').description).toBe('E'); }); }); }); diff --git a/packages/mermaid/src/types.ts b/packages/mermaid/src/types.ts index d1394e71b..477fb17b1 100644 --- a/packages/mermaid/src/types.ts +++ b/packages/mermaid/src/types.ts @@ -13,6 +13,18 @@ export interface NodeMetaData { ticket?: string; } +export interface ParticipantMetaData { + type?: + | 'actor' + | 'participant' + | 'boundary' + | 'control' + | 'entity' + | 'database' + | 'collections' + | 'queue'; +} + export interface EdgeMetaData { animation?: 'fast' | 'slow'; animate?: boolean; From bf362673fcb7397289dbdd7b3ce674f3788c7960 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:26:12 +0000 Subject: [PATCH 15/46] [autofix.ci] apply automated fixes --- docs/config/setup/mermaid/interfaces/ParseOptions.md | 4 ++-- docs/config/setup/mermaid/interfaces/ParseResult.md | 6 +++--- docs/config/setup/mermaid/interfaces/RenderResult.md | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/config/setup/mermaid/interfaces/ParseOptions.md b/docs/config/setup/mermaid/interfaces/ParseOptions.md index ea96f2706..e068a91fb 100644 --- a/docs/config/setup/mermaid/interfaces/ParseOptions.md +++ b/docs/config/setup/mermaid/interfaces/ParseOptions.md @@ -10,7 +10,7 @@ # Interface: ParseOptions -Defined in: [packages/mermaid/src/types.ts:72](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L72) +Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:72](https://github.com/mermaid-js/mer > `optional` **suppressErrors**: `boolean` -Defined in: [packages/mermaid/src/types.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L77) +Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) If `true`, parse will return `false` instead of throwing error when the diagram is invalid. The `parseError` function will not be called. diff --git a/docs/config/setup/mermaid/interfaces/ParseResult.md b/docs/config/setup/mermaid/interfaces/ParseResult.md index 7a5990610..1651a6fa9 100644 --- a/docs/config/setup/mermaid/interfaces/ParseResult.md +++ b/docs/config/setup/mermaid/interfaces/ParseResult.md @@ -10,7 +10,7 @@ # Interface: ParseResult -Defined in: [packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L80) +Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mer > **config**: [`MermaidConfig`](MermaidConfig.md) -Defined in: [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88) +Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100) The config passed as YAML frontmatter or directives @@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84) +Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96) The diagram type, e.g. 'flowchart', 'sequence', etc. diff --git a/docs/config/setup/mermaid/interfaces/RenderResult.md b/docs/config/setup/mermaid/interfaces/RenderResult.md index fc5fac4f5..c0e5496b8 100644 --- a/docs/config/setup/mermaid/interfaces/RenderResult.md +++ b/docs/config/setup/mermaid/interfaces/RenderResult.md @@ -10,7 +10,7 @@ # Interface: RenderResult -Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98) +Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mer > `optional` **bindFunctions**: (`element`) => `void` -Defined in: [packages/mermaid/src/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L116) +Defined in: [packages/mermaid/src/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L128) Bind function to be called after the svg has been inserted into the DOM. This is necessary for adding event listeners to the elements in the svg. @@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L106) +Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118) The diagram type, e.g. 'flowchart', 'sequence', etc. @@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc. > **svg**: `string` -Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L102) +Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114) The svg code for the rendered graph. From fb6ace73b53df309cd596b6c8c77dcc41d4a8196 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 8 Aug 2025 13:30:00 +0530 Subject: [PATCH 16/46] docs: updated documentation for new syntax on-behalf-of: @Mermaid-Chart --- docs/syntax/sequenceDiagram.md | 180 +++++++++--------- .../src/docs/syntax/sequenceDiagram.md | 96 +++++----- 2 files changed, 138 insertions(+), 138 deletions(-) diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md index b197f402b..32afe5a30 100644 --- a/docs/syntax/sequenceDiagram.md +++ b/docs/syntax/sequenceDiagram.md @@ -76,218 +76,218 @@ sequenceDiagram ### Boundary -If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below. +If you want to use the boundary symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ```mermaid sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ```mermaid-example sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ```mermaid sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ### Control -If you want to use the control symbol for a participant, use the `control` statement as shown below. +If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ```mermaid sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ```mermaid-example sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ```mermaid sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ### Entity -If you want to use the entity symbol for a participant, use the `entity` statement as shown below. +If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ```mermaid sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ```mermaid-example sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ```mermaid sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ### Database -If you want to use the database symbol for a participant, use the `database` statement as shown below. +If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ```mermaid sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ```mermaid-example sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ```mermaid sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ### Collections -If you want to use the collections symbol for a participant, use the `collections` statement as shown below. +If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ```mermaid sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ```mermaid-example sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ```mermaid sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ### Queue -If you want to use the queue symbol for a participant, use the `queue` statement as shown below. +If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ```mermaid sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ```mermaid-example sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ```mermaid sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ### Aliases diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index 63c6bfc8f..e2ff65af9 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -48,122 +48,122 @@ sequenceDiagram ### Boundary -If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below. +If you want to use the boundary symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ```mermaid sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ### Control -If you want to use the control symbol for a participant, use the `control` statement as shown below. +If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ```mermaid sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ### Entity -If you want to use the entity symbol for a participant, use the `entity` statement as shown below. +If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ```mermaid sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ### Database -If you want to use the database symbol for a participant, use the `database` statement as shown below. +If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ```mermaid sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ### Collections -If you want to use the collections symbol for a participant, use the `collections` statement as shown below. +If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ```mermaid sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ### Queue -If you want to use the queue symbol for a participant, use the `queue` statement as shown below. +If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ```mermaid sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ### Aliases From 716548548abe849da299a414e629233dab876913 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 8 Aug 2025 19:31:56 +0530 Subject: [PATCH 17/46] fix: rendering test cases on-behalf-of: @Mermaid-Chart --- .../rendering/sequencediagram-v2.spec.js | 583 +++++++++--------- .../src/diagrams/sequence/sequenceDb.ts | 2 +- 2 files changed, 300 insertions(+), 285 deletions(-) diff --git a/cypress/integration/rendering/sequencediagram-v2.spec.js b/cypress/integration/rendering/sequencediagram-v2.spec.js index 4b7fe88d5..f1c2aafbd 100644 --- a/cypress/integration/rendering/sequencediagram-v2.spec.js +++ b/cypress/integration/rendering/sequencediagram-v2.spec.js @@ -2,49 +2,50 @@ import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; const looks = ['classic']; const participantTypes = [ - 'participant', - 'actor', - 'boundary', - 'control', - 'entity', - 'database', - 'collections', - 'queue', + { type: 'participant', display: 'participant' }, + { type: 'actor', display: 'actor' }, + { type: 'boundary', display: 'boundary' }, + { type: 'control', display: 'control' }, + { type: 'entity', display: 'entity' }, + { type: 'database', display: 'database' }, + { type: 'collections', display: 'collections' }, + { type: 'queue', display: 'queue' }, ]; -const interactionTypes = [ - '->>', // Solid arrow with arrowhead - '-->>', // Dotted arrow with arrowhead - '->', // Solid arrow without arrowhead - '-->', // Dotted arrow without arrowhead - '-x', // Solid arrow with cross - '--x', // Dotted arrow with cross - '->>+', // Solid arrow with arrowhead (activate) - '-->>+', // Dotted arrow with arrowhead (activate) -]; +const restrictedTypes = ['boundary', 'control', 'entity', 'database', 'collections', 'queue']; + +const interactionTypes = ['->>', '-->>', '->', '-->', '-x', '--x', '->>+', '-->>+']; const notePositions = ['left of', 'right of', 'over']; +function getParticipantLine(name, type, alias) { + if (restrictedTypes.includes(type)) { + return ` participant ${name}@{ "type" : "${type}" }\n`; + } else if (alias) { + return ` participant ${name}@{ "type" : "${type}" } \n`; + } else { + return ` participant ${name}@{ "type" : "${type}" }\n`; + } +} + looks.forEach((look) => { describe(`Sequence Diagram Tests - ${look} look`, () => { it('should render all participant types', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.forEach((type, index) => { - diagramCode += ` ${type} ${type}${index} as ${type} ${index}\n`; + participantTypes.forEach((pt, index) => { + const name = `${pt.display}${index}`; + diagramCode += getParticipantLine(name, pt.type); }); - // Add some basic interactions for (let i = 0; i < participantTypes.length - 1; i++) { - diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; + diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`; } imgSnapshotTest(diagramCode, { look, sequence: { diagramMarginX: 50, diagramMarginY: 10 } }); }); it('should render all interaction types', () => { let diagramCode = `sequenceDiagram\n`; - // Create two participants - // Add all interaction types - diagramCode += ` participant A\n`; - diagramCode += ` participant B\n`; + diagramCode += getParticipantLine('A', 'actor'); + diagramCode += getParticipantLine('B', 'boundary'); interactionTypes.forEach((interaction, index) => { diagramCode += ` A ${interaction} B: ${interaction} message ${index}\n`; }); @@ -53,13 +54,14 @@ looks.forEach((look) => { it('should render participant creation and destruction', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.forEach((type, index) => { - diagramCode += ` ${type} A\n`; - diagramCode += ` ${type} B\n`; - diagramCode += ` create ${type} ${type}${index}\n`; - diagramCode += ` A ->> ${type}${index}: Hello ${type}\n`; + participantTypes.forEach((pt, index) => { + const name = `${pt.display}${index}`; + diagramCode += getParticipantLine('A', pt.type); + diagramCode += getParticipantLine('B', pt.type); + diagramCode += ` create participant ${name}@{ "type" : "${pt.type}" }\n`; + diagramCode += ` A ->> ${name}: Hello ${pt.display}\n`; if (index % 2 === 0) { - diagramCode += ` destroy ${type}${index}\n`; + diagramCode += ` destroy ${name}\n`; } }); imgSnapshotTest(diagramCode, { look }); @@ -67,8 +69,8 @@ looks.forEach((look) => { it('should render notes in all positions', () => { let diagramCode = `sequenceDiagram\n`; - diagramCode += ` participant A\n`; - diagramCode += ` participant B\n`; + diagramCode += getParticipantLine('A', 'actor'); + diagramCode += getParticipantLine('B', 'boundary'); notePositions.forEach((position, index) => { diagramCode += ` Note ${position} A: Note ${position} ${index}\n`; }); @@ -78,12 +80,12 @@ looks.forEach((look) => { it('should render parallel interactions', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.slice(0, 4).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(0, 4).forEach((pt, index) => { + diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type); }); diagramCode += ` par Parallel actions\n`; - for (let i = 0; i < participantTypes.length - 1; i += 2) { - diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; + for (let i = 0; i < 3; i += 2) { + diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`; if (i < participantTypes.length - 2) { diagramCode += ` and\n`; } @@ -94,8 +96,8 @@ looks.forEach((look) => { it('should render alternative flows', () => { let diagramCode = `sequenceDiagram\n`; - diagramCode += ` participant A\n`; - diagramCode += ` participant B\n`; + diagramCode += getParticipantLine('A', 'actor'); + diagramCode += getParticipantLine('B', 'boundary'); diagramCode += ` alt Successful case\n`; diagramCode += ` A ->> B: Request\n`; diagramCode += ` B -->> A: Success\n`; @@ -108,12 +110,12 @@ looks.forEach((look) => { it('should render loops', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.slice(0, 3).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(0, 3).forEach((pt, index) => { + diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type); }); diagramCode += ` loop For each participant\n`; for (let i = 0; i < 3; i++) { - diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Message ${i}\n`; + diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Message ${i}\n`; } diagramCode += ` end\n`; imgSnapshotTest(diagramCode, { look }); @@ -122,27 +124,26 @@ looks.forEach((look) => { it('should render boxes around groups', () => { let diagramCode = `sequenceDiagram\n`; diagramCode += ` box Group 1\n`; - participantTypes.slice(0, 3).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(0, 3).forEach((pt, index) => { + diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`; }); diagramCode += ` end\n`; diagramCode += ` box rgb(200,220,255) Group 2\n`; - participantTypes.slice(3, 6).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(3, 6).forEach((pt, index) => { + diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`; }); diagramCode += ` end\n`; - // Add some interactions - diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[3]}0: Cross-group message\n`; + diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[3].display}0: Cross-group message\n`; imgSnapshotTest(diagramCode, { look }); }); it('should render with different font settings', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.slice(0, 3).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(0, 3).forEach((pt, index) => { + diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type); }); - diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Regular message\n`; - diagramCode += ` Note right of ${participantTypes[1]}1: Regular note\n`; + diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Regular message\n`; + diagramCode += ` Note right of ${participantTypes[1].display}1: Regular note\n`; imgSnapshotTest(diagramCode, { look, sequence: { @@ -166,15 +167,15 @@ describe('Sequence Diagram Special Cases', () => { sequenceDiagram box rgb(200,220,255) Authentication actor User - boundary LoginUI - control AuthService - database UserDB + participant LoginUI@{ "type": "boundary" } + participant AuthService@{ "type": "control" } + participant UserDB@{ "type": "database" } end - + box rgb(200,255,220) Order Processing - entity Order - queue OrderQueue - collections AuditLogs + participant Order@{ "type": "entity" } + participant OrderQueue@{ "type": "queue" } + participant AuditLogs@{ "type": "collections" } end User ->> LoginUI: Enter credentials @@ -230,70 +231,87 @@ describe('Sequence Diagram Special Cases', () => { it('should render a sequence diagram with various participant types', () => { imgSnapshotTest( ` - sequenceDiagram - actor User - participant AuthService as Authentication Service - boundary UI - control OrderController - entity Product - database MongoDB - collections Products - queue OrderQueue - User ->> UI: Login request - UI ->> AuthService: Validate credentials - AuthService -->> UI: Authentication token - UI ->> OrderController: Place order - OrderController ->> Product: Check availability - Product -->> OrderController: Available - OrderController ->> MongoDB: Save order - MongoDB -->> OrderController: Order saved - OrderController ->> OrderQueue: Process payment - OrderQueue -->> User: Order confirmation + sequenceDiagram + participant User@{ "type": "actor" } + participant AuthService@{ "type": "control" } + participant UI@{ "type": "boundary" } + participant OrderController@{ "type": "control" } + participant Product@{ "type": "entity" } + participant MongoDB@{ "type": "database" } + participant Products@{ "type": "collections" } + participant OrderQueue@{ "type": "queue" } + User ->> UI: Login request + UI ->> AuthService: Validate credentials + AuthService -->> UI: Authentication token + UI ->> OrderController: Place order + OrderController ->> Product: Check availability + Product -->> OrderController: Available + OrderController ->> MongoDB: Save order + MongoDB -->> OrderController: Order saved + OrderController ->> OrderQueue: Process payment + OrderQueue -->> User: Order confirmation ` ); }); it('should render participant creation and destruction with different types', () => { - imgSnapshotTest( - ` - sequenceDiagram - actor Customer - participant Frontend - boundary PaymentGateway - Customer ->> Frontend: Place order - Frontend ->> OrderProcessor: Process order - create database OrderDB - OrderProcessor ->> OrderDB: Save order - ` - ); + imgSnapshotTest(` + sequenceDiagram + participant Alice@{ "type" : "boundary" } + Alice->>Bob: Hello Bob, how are you ? + Bob->>Alice: Fine, thank you. And you? + create participant Carl@{ "type" : "control" } + Alice->>Carl: Hi Carl! + create actor D as Donald + Carl->>D: Hi! + destroy Carl + Alice-xCarl: We are too many + destroy Bob + Bob->>Alice: I agree + `); }); it('should handle complex interactions between different participant types', () => { imgSnapshotTest( ` - sequenceDiagram - box rgba(200,220,255,0.5) System Components - actor User - boundary WebUI - control API - entity BusinessLogic - database MainDB - end - box rgba(200,255,220,0.5) External Services - queue MessageQueue - database AuditLogs - end + sequenceDiagram + box rgb(200,220,255) Authentication + participant User@{ "type": "actor" } + participant LoginUI@{ "type": "boundary" } + participant AuthService@{ "type": "control" } + participant UserDB@{ "type": "database" } + end - User ->> WebUI: Submit request - WebUI ->> API: Process request - API ->> BusinessLogic: Execute business rules - BusinessLogic ->> MainDB: Query data - MainDB -->> BusinessLogic: Return results - BusinessLogic ->> MessageQueue: Publish event - MessageQueue -->> AuditLogs: Store audit trail - AuditLogs -->> API: Audit complete - API -->> WebUI: Return response - WebUI -->> User: Show results + box rgb(200,255,220) Order Processing + participant Order@{ "type": "entity" } + participant OrderQueue@{ "type": "queue" } + participant AuditLogs@{ "type": "collections" } + end + + User ->> LoginUI: Enter credentials + LoginUI ->> AuthService: Validate + AuthService ->> UserDB: Query user + UserDB -->> AuthService: User data + + alt Valid credentials + AuthService -->> LoginUI: Success + LoginUI -->> User: Welcome + + par Place order + User ->> Order: New order + Order ->> OrderQueue: Process + and + Order ->> AuditLogs: Record + end + + loop Until confirmed + OrderQueue ->> Order: Update status + Order -->> User: Notification + end + else Invalid credentials + AuthService --x LoginUI: Failure + LoginUI --x User: Retry + end `, { sequence: { useMaxWidth: false } } ); @@ -302,15 +320,15 @@ describe('Sequence Diagram Special Cases', () => { it('should render parallel processes with different participant types', () => { imgSnapshotTest( ` - sequenceDiagram - actor Customer - participant Frontend - boundary PaymentService - control InventoryManager - entity Order - database OrdersDB - queue NotificationQueue - + sequenceDiagram + participant Customer@{ "type": "actor" } + participant Frontend@{ "type": "participant" } + participant PaymentService@{ "type": "boundary" } + participant InventoryManager@{ "type": "control" } + participant Order@{ "type": "entity" } + participant OrdersDB@{ "type": "database" } + participant NotificationQueue@{ "type": "queue" } + Customer ->> Frontend: Place order Frontend ->> Order: Create order par Parallel Processing @@ -327,18 +345,18 @@ describe('Sequence Diagram Special Cases', () => { ` ); }); - - it('should render different participant types with notes and loops', () => { - imgSnapshotTest( - ` + }); + it('should render different participant types with notes and loops', () => { + imgSnapshotTest( + ` sequenceDiagram actor Admin participant Dashboard - boundary AuthService - control UserManager - entity UserProfile - database UserDB - database Logs + participant AuthService@{ "type" : "boundary" } + participant UserManager@{ "type" : "control" } + participant UserProfile@{ "type" : "entity" } + participant UserDB@{ "type" : "database" } + participant Logs@{ "type" : "database" } Admin ->> Dashboard: Open user management loop Authentication check @@ -354,217 +372,214 @@ describe('Sequence Diagram Special Cases', () => { UserManager ->> Dashboard: Display users Dashboard ->> Logs: Record access Logs ->> Admin: Audit trail - ` - ); - }); + ` + ); + }); - it('should render different participant types with alternative flows', () => { - imgSnapshotTest( - ` - sequenceDiagram - actor Client - participant MobileApp - boundary CloudService - control DataProcessor - entity Transaction - database TransactionsDB - queue EventBus - - Client ->> MobileApp: Initiate transaction - MobileApp ->> CloudService: Authenticate - alt Authentication successful - CloudService -->> MobileApp: Auth token - MobileApp ->> DataProcessor: Process data - DataProcessor ->> Transaction: Create transaction - Transaction ->> TransactionsDB: Save record - TransactionsDB -->> Transaction: Confirmation - Transaction ->> EventBus: Publish event - EventBus -->> Client: Notification - else Authentication failed - CloudService -->> MobileApp: Error - MobileApp -->> Client: Show error - end + it('should render different participant types with alternative flows', () => { + imgSnapshotTest( ` - ); - }); + sequenceDiagram + actor Client + participant MobileApp + participant CloudService@{ "type" : "boundary" } + participant DataProcessor@{ "type" : "control" } + participant Transaction@{ "type" : "entity" } + participant TransactionsDB@{ "type" : "database" } + participant EventBus@{ "type" : "queue" } + + Client ->> MobileApp: Initiate transaction + MobileApp ->> CloudService: Authenticate + alt Authentication successful + CloudService -->> MobileApp: Auth token + MobileApp ->> DataProcessor: Process data + DataProcessor ->> Transaction: Create transaction + Transaction ->> TransactionsDB: Save record + TransactionsDB -->> Transaction: Confirmation + Transaction ->> EventBus: Publish event + EventBus -->> Client: Notification + else Authentication failed + CloudService -->> MobileApp: Error + MobileApp -->> Client: Show error + end + ` + ); + }); - it('should render different participant types with wrapping text', () => { - imgSnapshotTest( - ` - sequenceDiagram - actor LongNameUser as User With A Very
Long Name - participant FE as Frontend Service
With Long Name - boundary B as Boundary With
Multiline Name - control C as Control With
Multiline Name - entity E as Entity With
Multiline Name - database DB as Database With
Multiline Name - collections COL as Collections With
Multiline Name - queue Q as Queue With
Multiline Name - - LongNameUser ->> FE: This is a very long message that should wrap properly in the diagram - FE ->> B: Another long message
with explicit
line breaks - B -->> FE: Response message that is also quite long and needs to wrap - FE ->> C: Process data - C ->> E: Validate - E -->> C: Validation result - C ->> DB: Save - DB -->> C: Save result - C ->> COL: Log - COL -->> Q: Forward - Q -->> LongNameUser: Final response with confirmation of all actions taken - `, - { sequence: { wrap: true } } - ); - }); - describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => { - it('should render long notes left of boundary', () => { - imgSnapshotTest( - ` - sequenceDiagram - boundary Alice - actor Bob - Alice->>Bob: Hola - Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be - Bob->>Alice: I'm short though + it('should render different participant types with wrapping text', () => { + imgSnapshotTest( + ` + sequenceDiagram + participant B@{ "type" : "boundary" } + participant C@{ "type" : "control" } + participant E@{ "type" : "entity" } + participant DB@{ "type" : "database" } + participant COL@{ "type" : "collections" } + participant Q@{ "type" : "queue" } + + FE ->> B: Another long message
with explicit
line breaks + B -->> FE: Response message that is also quite long and needs to wrap + FE ->> C: Process data + C ->> E: Validate + E -->> C: Validation result + C ->> DB: Save + DB -->> C: Save result + C ->> COL: Log + COL -->> Q: Forward + Q -->> LongNameUser: Final response with confirmation of all actions taken `, - {} - ); - }); + { sequence: { wrap: true } } + ); + }); - it('should render wrapped long notes left of control', () => { - imgSnapshotTest( - ` + describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => { + it('should render long notes left of boundary', () => { + imgSnapshotTest( + ` sequenceDiagram - control Alice + participant Alice@{ "type" : "boundary" } + actor Bob + Alice->>Bob: Hola + Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long notes left of control', () => { + imgSnapshotTest( + ` + sequenceDiagram + participant Alice@{ "type" : "control" } actor Bob Alice->>Bob: Hola Note left of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render long notes right of entity', () => { - imgSnapshotTest( - ` + it('should render long notes right of entity', () => { + imgSnapshotTest( + ` sequenceDiagram - entity Alice + participant Alice@{ "type" : "entity" } actor Bob Alice->>Bob: Hola Note right of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render wrapped long notes right of database', () => { - imgSnapshotTest( - ` + it('should render wrapped long notes right of database', () => { + imgSnapshotTest( + ` sequenceDiagram - database Alice + participant Alice@{ "type" : "database" } actor Bob Alice->>Bob: Hola Note right of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render long notes over collections', () => { - imgSnapshotTest( - ` + it('should render long notes over collections', () => { + imgSnapshotTest( + ` sequenceDiagram - collections Alice + participant Alice@{ "type" : "collections" } actor Bob Alice->>Bob: Hola Note over Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render wrapped long notes over queue', () => { - imgSnapshotTest( - ` + it('should render wrapped long notes over queue', () => { + imgSnapshotTest( + ` sequenceDiagram - queue Alice + participant Alice@{ "type" : "queue" } actor Bob Alice->>Bob: Hola Note over Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render notes over actor and boundary', () => { - imgSnapshotTest( - ` + it('should render notes over actor and boundary', () => { + imgSnapshotTest( + ` sequenceDiagram actor Alice - boundary Charlie + participant Charlie@{ "type" : "boundary" } note over Alice: Some note note over Charlie: Other note `, - {} - ); - }); + {} + ); + }); - it('should render long messages from database to collections', () => { - imgSnapshotTest( - ` + it('should render long messages from database to collections', () => { + imgSnapshotTest( + ` sequenceDiagram - database Alice - collections Bob + participant Alice@{ "type" : "database" } + participant Bob@{ "type" : "collections" } Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render wrapped long messages from control to entity', () => { - imgSnapshotTest( - ` + it('should render wrapped long messages from control to entity', () => { + imgSnapshotTest( + ` sequenceDiagram - control Alice - entity Bob + participant Alice@{ "type" : "control" } + participant Bob@{ "type" : "entity" } Alice->>Bob:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render long messages from queue to boundary', () => { - imgSnapshotTest( - ` + it('should render long messages from queue to boundary', () => { + imgSnapshotTest( + ` sequenceDiagram - queue Alice - boundary Bob + participant Alice@{ "type" : "queue" } + participant Bob@{ "type" : "boundary" } Alice->>Bob: I'm short Bob->>Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be `, - {} - ); - }); + {} + ); + }); - it('should render wrapped long messages from actor to database', () => { - imgSnapshotTest( - ` + it('should render wrapped long messages from actor to database', () => { + imgSnapshotTest( + ` sequenceDiagram actor Alice - database Bob + participant Bob@{ "type" : "database" } Alice->>Bob: I'm short Bob->>Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be `, - {} - ); - }); + {} + ); }); }); @@ -574,8 +589,8 @@ describe('Sequence Diagram Special Cases', () => { ` sequenceDiagram actor Alice - boundary Bob - control John as John
Second Line + participant Bob@{ "type" : "boundary" } + participant John@{ "type" : "control" } Alice ->> Bob: Hello Bob, how are you? Bob-->>John: How about you John? Bob--x Alice: I am good thanks! @@ -584,9 +599,9 @@ describe('Sequence Diagram Special Cases', () => { Bob-->Alice: Checking with John... alt either this Alice->>John: Yes - else or this + else or this Alice->>John: No - else or this will happen + else or this will happen Alice->John: Maybe end par this happens in parallel @@ -611,8 +626,8 @@ describe('Sequence Diagram Special Cases', () => { ` sequenceDiagram actor Alice - boundary Bob - control John as John
Second Line + participant Bob@{ "type" : "boundary" } + participant John@{ "type" : "control" } Alice ->> Bob: Hello Bob, how are you? Bob-->>John: How about you John? Bob--x Alice: I am good thanks! @@ -621,9 +636,9 @@ describe('Sequence Diagram Special Cases', () => { Bob-->Alice: Checking with John... alt either this Alice->>John: Yes - else or this + else or this Alice->>John: No - else or this will happen + else or this will happen Alice->John: Maybe end par this happens in parallel diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index 4f06a1ebe..67ae19de5 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -553,7 +553,7 @@ export class SequenceDB implements DiagramDB { ); } this.state.records.lastCreated = param.actor; - this.addActor(param.actor, param.actor, param.description, param.draw); + this.addActor(param.actor, param.actor, param.description, param.draw, param.config); this.state.records.createdActors.set(param.actor, this.state.records.messages.length); break; case 'destroyParticipant': From e4d3aa461000bdb9be1034989d8867d7aa829a97 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 8 Aug 2025 20:18:21 +0530 Subject: [PATCH 18/46] fix: refactored documentation, test cases and sequenceDiagram.jison file on-behalf-of: @Mermaid-Chart --- .../setup/mermaid/interfaces/ParseOptions.md | 4 +- .../setup/mermaid/interfaces/ParseResult.md | 6 +- .../setup/mermaid/interfaces/RenderResult.md | 8 +- docs/syntax/sequenceDiagram.md | 96 ------------------- .../sequence/parser/sequenceDiagram.jison | 2 +- .../diagrams/sequence/sequenceDiagram.spec.js | 6 +- .../src/docs/syntax/sequenceDiagram.md | 48 ---------- 7 files changed, 15 insertions(+), 155 deletions(-) diff --git a/docs/config/setup/mermaid/interfaces/ParseOptions.md b/docs/config/setup/mermaid/interfaces/ParseOptions.md index e068a91fb..0ff3bf004 100644 --- a/docs/config/setup/mermaid/interfaces/ParseOptions.md +++ b/docs/config/setup/mermaid/interfaces/ParseOptions.md @@ -10,7 +10,7 @@ # Interface: ParseOptions -Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84) +Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mer > `optional` **suppressErrors**: `boolean` -Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) +Defined in: [packages/mermaid/src/types.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L94) If `true`, parse will return `false` instead of throwing error when the diagram is invalid. The `parseError` function will not be called. diff --git a/docs/config/setup/mermaid/interfaces/ParseResult.md b/docs/config/setup/mermaid/interfaces/ParseResult.md index 1651a6fa9..66e5de194 100644 --- a/docs/config/setup/mermaid/interfaces/ParseResult.md +++ b/docs/config/setup/mermaid/interfaces/ParseResult.md @@ -10,7 +10,7 @@ # Interface: ParseResult -Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92) +Defined in: [packages/mermaid/src/types.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L97) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mer > **config**: [`MermaidConfig`](MermaidConfig.md) -Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100) +Defined in: [packages/mermaid/src/types.ts:105](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L105) The config passed as YAML frontmatter or directives @@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96) +Defined in: [packages/mermaid/src/types.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L101) The diagram type, e.g. 'flowchart', 'sequence', etc. diff --git a/docs/config/setup/mermaid/interfaces/RenderResult.md b/docs/config/setup/mermaid/interfaces/RenderResult.md index c0e5496b8..9d6771ad1 100644 --- a/docs/config/setup/mermaid/interfaces/RenderResult.md +++ b/docs/config/setup/mermaid/interfaces/RenderResult.md @@ -10,7 +10,7 @@ # Interface: RenderResult -Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110) +Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L115) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/me > `optional` **bindFunctions**: (`element`) => `void` -Defined in: [packages/mermaid/src/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L128) +Defined in: [packages/mermaid/src/types.ts:133](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L133) Bind function to be called after the svg has been inserted into the DOM. This is necessary for adding event listeners to the elements in the svg. @@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118) +Defined in: [packages/mermaid/src/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L123) The diagram type, e.g. 'flowchart', 'sequence', etc. @@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc. > **svg**: `string` -Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114) +Defined in: [packages/mermaid/src/types.ts:119](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L119) The svg code for the rendered graph. diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md index 32afe5a30..a8f502dcd 100644 --- a/docs/syntax/sequenceDiagram.md +++ b/docs/syntax/sequenceDiagram.md @@ -94,22 +94,6 @@ sequenceDiagram Bob->>Alice: Response to boundary ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "boundary" } - participant Bob - Alice->>Bob: Request from boundary - Bob->>Alice: Response to boundary -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "boundary" } - participant Bob - Alice->>Bob: Request from boundary - Bob->>Alice: Response to boundary -``` - ### Control If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below. @@ -130,22 +114,6 @@ sequenceDiagram Bob->>Alice: Control response ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "control" } - participant Bob - Alice->>Bob: Control request - Bob->>Alice: Control response -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "control" } - participant Bob - Alice->>Bob: Control request - Bob->>Alice: Control response -``` - ### Entity If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below. @@ -166,22 +134,6 @@ sequenceDiagram Bob->>Alice: Entity response ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "entity" } - participant Bob - Alice->>Bob: Entity request - Bob->>Alice: Entity response -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "entity" } - participant Bob - Alice->>Bob: Entity request - Bob->>Alice: Entity response -``` - ### Database If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below. @@ -202,22 +154,6 @@ sequenceDiagram Bob->>Alice: DB result ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "database" } - participant Bob - Alice->>Bob: DB query - Bob->>Alice: DB result -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "database" } - participant Bob - Alice->>Bob: DB query - Bob->>Alice: DB result -``` - ### Collections If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below. @@ -238,22 +174,6 @@ sequenceDiagram Bob->>Alice: Collections response ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "collections" } - participant Bob - Alice->>Bob: Collections request - Bob->>Alice: Collections response -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "collections" } - participant Bob - Alice->>Bob: Collections request - Bob->>Alice: Collections response -``` - ### Queue If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below. @@ -274,22 +194,6 @@ sequenceDiagram Bob->>Alice: Queue response ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "queue" } - participant Bob - Alice->>Bob: Queue message - Bob->>Alice: Queue response -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "queue" } - participant Bob - Alice->>Bob: Queue message - Bob->>Alice: Queue response -``` - ### Aliases The actor can have a convenient identifier and a descriptive label. diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 87ef8212f..5938e3247 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -235,7 +235,7 @@ participant_statement | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} - | 'participant' actor_with_config 'NEWLINE' { $2.draw = 'participant'; $2.type = 'addParticipant'; $$ = $2;} + | 'participant' actor_with_config 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant'; $$=$2;} ; diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 8088f3c44..4820d19ea 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2038,13 +2038,17 @@ Bob->>Alice:Got it! expect(messages[0].from).toBe('Alice'); expect(messages[0].to).toBe('Bob'); }); - describe('when newly parsing messages ', () => { + describe('when parsing extended participant syntax ', () => { it('should parse a message', async () => { const actor1 = 'database'; const diagram = await Diagram.fromText(` sequenceDiagram participant Alice@{ "type" : "database" } participant Bob@{ "type" : "database" } + participant Carl@{ type: "database" } + participant David@{ "type" : 'database' } + participant Eve@{ type: 'database' } + participant Favela@{ "type" : "database" } Bob->>+Alice: Hi Alice Alice->>+Bob: Hi Bob `); diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index e2ff65af9..6e0ac87bf 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -58,14 +58,6 @@ sequenceDiagram Bob->>Alice: Response to boundary ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "boundary" } - participant Bob - Alice->>Bob: Request from boundary - Bob->>Alice: Response to boundary -``` - ### Control If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below. @@ -78,14 +70,6 @@ sequenceDiagram Bob->>Alice: Control response ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "control" } - participant Bob - Alice->>Bob: Control request - Bob->>Alice: Control response -``` - ### Entity If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below. @@ -98,14 +82,6 @@ sequenceDiagram Bob->>Alice: Entity response ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "entity" } - participant Bob - Alice->>Bob: Entity request - Bob->>Alice: Entity response -``` - ### Database If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below. @@ -118,14 +94,6 @@ sequenceDiagram Bob->>Alice: DB result ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "database" } - participant Bob - Alice->>Bob: DB query - Bob->>Alice: DB result -``` - ### Collections If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below. @@ -138,14 +106,6 @@ sequenceDiagram Bob->>Alice: Collections response ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "collections" } - participant Bob - Alice->>Bob: Collections request - Bob->>Alice: Collections response -``` - ### Queue If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below. @@ -158,14 +118,6 @@ sequenceDiagram Bob->>Alice: Queue response ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "queue" } - participant Bob - Alice->>Bob: Queue message - Bob->>Alice: Queue response -``` - ### Aliases The actor can have a convenient identifier and a descriptive label. From 769b362005889868daa1317f03fe354a651468e4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:53:47 +0000 Subject: [PATCH 19/46] [autofix.ci] apply automated fixes --- docs/config/setup/mermaid/interfaces/ParseOptions.md | 4 ++-- docs/config/setup/mermaid/interfaces/ParseResult.md | 6 +++--- docs/config/setup/mermaid/interfaces/RenderResult.md | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/config/setup/mermaid/interfaces/ParseOptions.md b/docs/config/setup/mermaid/interfaces/ParseOptions.md index 0ff3bf004..e068a91fb 100644 --- a/docs/config/setup/mermaid/interfaces/ParseOptions.md +++ b/docs/config/setup/mermaid/interfaces/ParseOptions.md @@ -10,7 +10,7 @@ # Interface: ParseOptions -Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) +Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mer > `optional` **suppressErrors**: `boolean` -Defined in: [packages/mermaid/src/types.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L94) +Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) If `true`, parse will return `false` instead of throwing error when the diagram is invalid. The `parseError` function will not be called. diff --git a/docs/config/setup/mermaid/interfaces/ParseResult.md b/docs/config/setup/mermaid/interfaces/ParseResult.md index 66e5de194..1651a6fa9 100644 --- a/docs/config/setup/mermaid/interfaces/ParseResult.md +++ b/docs/config/setup/mermaid/interfaces/ParseResult.md @@ -10,7 +10,7 @@ # Interface: ParseResult -Defined in: [packages/mermaid/src/types.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L97) +Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:97](https://github.com/mermaid-js/mer > **config**: [`MermaidConfig`](MermaidConfig.md) -Defined in: [packages/mermaid/src/types.ts:105](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L105) +Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100) The config passed as YAML frontmatter or directives @@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L101) +Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96) The diagram type, e.g. 'flowchart', 'sequence', etc. diff --git a/docs/config/setup/mermaid/interfaces/RenderResult.md b/docs/config/setup/mermaid/interfaces/RenderResult.md index 9d6771ad1..c0e5496b8 100644 --- a/docs/config/setup/mermaid/interfaces/RenderResult.md +++ b/docs/config/setup/mermaid/interfaces/RenderResult.md @@ -10,7 +10,7 @@ # Interface: RenderResult -Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L115) +Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/me > `optional` **bindFunctions**: (`element`) => `void` -Defined in: [packages/mermaid/src/types.ts:133](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L133) +Defined in: [packages/mermaid/src/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L128) Bind function to be called after the svg has been inserted into the DOM. This is necessary for adding event listeners to the elements in the svg. @@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L123) +Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118) The diagram type, e.g. 'flowchart', 'sequence', etc. @@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc. > **svg**: `string` -Defined in: [packages/mermaid/src/types.ts:119](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L119) +Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114) The svg code for the rendered graph. From e7a7ff8a2a44df14c131e44d17f5e5d3f44b5616 Mon Sep 17 00:00:00 2001 From: omkarht Date: Mon, 11 Aug 2025 13:20:49 +0530 Subject: [PATCH 20/46] fix: refactored code on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/sequence/svgDraw.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index f6aa3d34b..95863234d 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -706,10 +706,9 @@ const drawActorTypeControl = function (elem, actor, conf, isFooter) { // Draw looping arrow as arc path actElem - .append('path') - .attr('d', `M ${cx},${cy - r}`) - .attr('stroke-width', 1.5) - .attr('marker-end', 'url(#filled-head-control)'); + .append('line') + .attr('marker-end', 'url(#filled-head-control)') + .attr('transform', `translate(${cx}, ${cy - r})`); const bounds = actElem.node().getBBox(); actor.height = bounds.height + 2 * (conf?.sequence?.labelBoxHeight ?? 0); @@ -775,7 +774,7 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { .attr('stroke-width', 2); const bounds = actElem.node().getBBox(); - actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); + actor.height = bounds.height + (conf?.sequence?.labelBoxHeight ?? 0); if (!isFooter) { actorCnt++; From 9f6ee53382e817a50f3063d9c1a6161ad0123206 Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 12 Aug 2025 14:20:47 +0530 Subject: [PATCH 21/46] fix: refactored parsing logic for new syntax on-behalf-of: @Mermaid-Chart --- .../mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 43c62a0cc..13e63f3ae 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -31,7 +31,7 @@ \@\{ { this.begin('CONFIG'); return 'CONFIG_START'; } [^\}]+ { return 'CONFIG_CONTENT'; } \} { this.popState(); this.popState(); return 'CONFIG_END'; } -[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; } +[^\<->\->:\n,;@\s]+(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; } [^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } From 80c6faf4d50c016a1f2fa04d5fb70b221171cc4e Mon Sep 17 00:00:00 2001 From: RSS1102 Date: Tue, 12 Aug 2025 19:36:55 +0800 Subject: [PATCH 22/46] chore: upgrade `unocss@66.4.2` and `@iconify/utils@3.0.1` --- packages/mermaid/package.json | 2 +- packages/mermaid/src/docs/package.json | 2 +- pnpm-lock.yaml | 446 ++++++++++++++----------- 3 files changed, 256 insertions(+), 194 deletions(-) diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 5a9669ff6..efcaee92a 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -68,7 +68,7 @@ }, "dependencies": { "@braintree/sanitize-url": "^7.0.4", - "@iconify/utils": "^2.1.33", + "@iconify/utils": "3.0.1", "@mermaid-js/parser": "workspace:^", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", diff --git a/packages/mermaid/src/docs/package.json b/packages/mermaid/src/docs/package.json index 6ec43eb93..cb54f2ca7 100644 --- a/packages/mermaid/src/docs/package.json +++ b/packages/mermaid/src/docs/package.json @@ -31,7 +31,7 @@ "fast-glob": "^3.3.3", "https-localhost": "^4.7.1", "pathe": "^2.0.3", - "unocss": "^66.0.0", + "unocss": "66.4.2", "unplugin-vue-components": "^28.4.0", "vite": "^6.1.1", "vite-plugin-pwa": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8be2d5aca..33637a788 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: patchedDependencies: roughjs: - hash: 3543d47108cb41b68ec6a671c0e1f9d0cfe2ce524fea5b0992511ae84c3c6b64 + hash: vxb6t6fqvzyhwhtjiliqr25jyq path: patches/roughjs.patch importers: @@ -227,8 +227,8 @@ importers: specifier: ^7.0.4 version: 7.1.0 '@iconify/utils': - specifier: ^2.1.33 - version: 2.3.0 + specifier: 3.0.1 + version: 3.0.1 '@mermaid-js/parser': specifier: workspace:^ version: link:../parser @@ -273,7 +273,7 @@ importers: version: 16.0.0 roughjs: specifier: ^4.6.6 - version: 4.6.6(patch_hash=3543d47108cb41b68ec6a671c0e1f9d0cfe2ce524fea5b0992511ae84c3c6b64) + version: 4.6.6(patch_hash=vxb6t6fqvzyhwhtjiliqr25jyq) stylis: specifier: ^4.3.6 version: 4.3.6 @@ -499,8 +499,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 unocss: - specifier: ^66.0.0 - version: 66.0.0(postcss@8.5.6)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3)) + specifier: 66.4.2 + version: 66.4.2(postcss@8.5.6)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)) unplugin-vue-components: specifier: ^28.4.0 version: 28.4.0(@babel/parser@7.28.0)(vue@3.5.13(typescript@5.7.3)) @@ -647,11 +647,11 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@antfu/install-pkg@1.0.0': - resolution: {integrity: sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@antfu/utils@8.1.1': - resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} + '@antfu/utils@9.2.0': + resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==} '@apideck/better-ajv-errors@0.3.6': resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} @@ -2460,8 +2460,8 @@ packages: '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@2.3.0': - resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + '@iconify/utils@3.0.1': + resolution: {integrity: sha512-A78CUEnFGX8I/WlILxJCuIJXloL0j/OJ9PSchPAfCargEIKmUBWvvEMmKWB5oONwiUqlNt+5eRufdkLxeHIWYw==} '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} @@ -2740,6 +2740,9 @@ packages: '@polka/url@1.0.0-next.28': resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + '@quansync/fs@0.1.4': + resolution: {integrity: sha512-vy/41FCdnIalPTQCb2Wl0ic1caMdzGus4ktDp+gpZesQNydXcx8nhh8qB3qMPbGkictOTaXgXEUUfQEm8DQYoA==} + '@react-aria/focus@3.21.0': resolution: {integrity: sha512-7NEGtTPsBy52EZ/ToVKCu0HSelE3kq9qeis+2eEq90XSuJOMaDHUQrA7RC2Y89tlEwQB31bud/kKRi9Qme1dkA==} peerDependencies: @@ -3538,88 +3541,94 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@unocss/astro@66.0.0': - resolution: {integrity: sha512-GBhXT6JPqXjDXoJZTXhySk83NgOt0UigChqrUUdG4x7Z+DVYkDBION8vZUJjw0OdIaxNQ4euGWu4GDsMF6gQQg==} + '@unocss/astro@66.4.2': + resolution: {integrity: sha512-En3AKHwkiPxtZT95vkVrNiRYrB+DFVCikew6/dMMCWDWVKK0+5tEVUTzR1ak3+YnzAXl0NpWj8D4zHb0PxOs/A==} peerDependencies: - vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 peerDependenciesMeta: vite: optional: true - '@unocss/cli@66.0.0': - resolution: {integrity: sha512-KVQiskoOjVkLVpNaG6WpLa4grPplrZROYZJVIUYSTqZyZRFNSvjttHcsCwpoWUEUdEombPtVZl8FrXePjY5IiQ==} + '@unocss/cli@66.4.2': + resolution: {integrity: sha512-WsXzrB0SHbSt2nOHtD5QM91VN8j38+wObqyGcoIhtBSugqzsc+t7AdPkxV/ZaYgtPAz87bR0WFEVKcbiBRnmJw==} engines: {node: '>=14'} hasBin: true - '@unocss/config@66.0.0': - resolution: {integrity: sha512-nFRGop/guBa4jLkrgXjaRDm5JPz4x3YpP10m5IQkHpHwlnHUVn1L9smyPl04ohYWhYn9ZcAHgR28Ih2jwta8hw==} + '@unocss/config@66.4.2': + resolution: {integrity: sha512-plji1gNGSzlWjuV2Uh0q6Dt5ZlNkOKCHpgxekW9J458WghGAMBeXgB9uNpWg6flilqP1g0GJQv+XvJcSkYRGpQ==} engines: {node: '>=14'} - '@unocss/core@66.0.0': - resolution: {integrity: sha512-PdVbSMHNDDkr++9nkqzsZRAkaU84gxMTEgYbqI7dt2p1DXp/5tomVtmMsr2/whXGYKRiUc0xZ3p4Pzraz8TcXA==} + '@unocss/core@66.4.2': + resolution: {integrity: sha512-cYgMQrLhB9nRekv5c+yPDDa+5dzlMkA2UMQRil0s5D9Lb5n7NsCMcr6+nfxkcSYVLy92SbwDV45c6T7vIxFTOA==} - '@unocss/extractor-arbitrary-variants@66.0.0': - resolution: {integrity: sha512-vlkOIOuwBfaFBJcN6o7+obXjigjOlzVFN/jT6pG1WXbQDTRZ021jeF3i9INdb9D/0cQHSeDvNgi1TJ5oUxfiow==} + '@unocss/extractor-arbitrary-variants@66.4.2': + resolution: {integrity: sha512-T/eSeodfAp7HaWnQGqVLOsW4PbKUAvuybNRyvFWThMneM2qo+dOo3kFnA5my9ULAmRSFsAlyB1DnupD3qv5Klg==} - '@unocss/inspector@66.0.0': - resolution: {integrity: sha512-mkIxieVm0kMOKw+E4ABpIerihYMdjgq9A92RD5h2+W/ebpxTEw5lTTK1xcMLiAlmOrVYMQKjpgPeu3vQmDyGZQ==} + '@unocss/inspector@66.4.2': + resolution: {integrity: sha512-ugcJK8r2ypM4eIdgetVn8RhfKrbA3AF3OQ/RohK5PPk2UPDAScqabzYpfdNW4eYQsBOZOgoiqWtnfc8weqo8LQ==} - '@unocss/postcss@66.0.0': - resolution: {integrity: sha512-6bi+ujzh8I1PJwtmHX71LH8z/H9+vPxeYD4XgFihyU1k4Y6MVhjr7giGjLX4yP27IP+NsVyotD22V7by/dBVEA==} + '@unocss/postcss@66.4.2': + resolution: {integrity: sha512-tu4lnh6K27pIAuaQHlFlhXin8korwC0r1kQl00YMmF3THiX7orXkTP6xWGcQwnkbx4uQz1dw+tBimYxeaAMrhA==} engines: {node: '>=14'} peerDependencies: postcss: ^8.4.21 - '@unocss/preset-attributify@66.0.0': - resolution: {integrity: sha512-eYsOgmcDoiIgGAepIwRX+DKGYxc/wm0r4JnDuZdz29AB+A6oY/FGHS1BVt4rq9ny4B5PofP4p6Rty+vwD9rigw==} + '@unocss/preset-attributify@66.4.2': + resolution: {integrity: sha512-DwFJJkkawmHpjo3pGQE8FyoPsvhbxh+QMvvaAdYpo+iZ5HRkeDml9SOj7u6SGTcmbNyI+QR61s0KM8fxx6HcVQ==} - '@unocss/preset-icons@66.0.0': - resolution: {integrity: sha512-6ObwTvEGuPBbKWRoMMiDioHtwwQTFI5oojFLJ32Y8tW6TdXvBLkO88d7qpgQxEjgVt4nJrqF1WEfR4niRgBm0Q==} + '@unocss/preset-icons@66.4.2': + resolution: {integrity: sha512-qJx9gmesrvrmoTe9Mqoidihad8hm2MSD4QAezhfDSAyllioJOgyT0Bev/IEWAbehe9jtqYIh8v1oCerBPbGn6Q==} - '@unocss/preset-mini@66.0.0': - resolution: {integrity: sha512-d62eACnuKtR0dwCFOQXgvw5VLh5YSyK56xCzpHkh0j0GstgfDLfKTys0T/XVAAvdSvAy/8A8vhSNJ4PlIc9V2A==} + '@unocss/preset-mini@66.4.2': + resolution: {integrity: sha512-Ry+5hM+XLmT8HrEb182mUfcZuyrZ8xR+TBe72DBcliJ1DhOV3K67TCxwQucfb0zHbGV71HNWdPmHsLKxPDgweQ==} - '@unocss/preset-tagify@66.0.0': - resolution: {integrity: sha512-GGYGyWxaevh0jN0NoATVO1Qe7DFXM3ykLxchlXmG6/zy963pZxItg/njrKnxE9la4seCdxpFH7wQBa68imwwdA==} + '@unocss/preset-tagify@66.4.2': + resolution: {integrity: sha512-dECS09LqWJY4sYpgPUH2OAUftWU/tiZPR2XDRoTngeGU37GxSN+1sWtSmB7vwDm3C7opsdVUN20he8F1LUNubw==} - '@unocss/preset-typography@66.0.0': - resolution: {integrity: sha512-apjckP5nPU5mtaHTCzz5u/dK9KJWwJ2kOFCVk0+a/KhUWmnqnzmjRYZlEuWxxr5QxTdCW+9cIoRDSA0lYZS5tg==} + '@unocss/preset-typography@66.4.2': + resolution: {integrity: sha512-ZOKRuR5+V0r30QTVq04/6ZoIw75me3V25v2dU2YWJXIzwpMKmQ9TUN/M1yeiEUFfXjOaruWX6Ad6CvAw2MlCew==} - '@unocss/preset-uno@66.0.0': - resolution: {integrity: sha512-qgoZ/hzTI32bQvcyjcwvv1X/dbPlmQNehzgjUaL7QFT0q0/CN/SRpysfzoQ8DLl2se9T+YCOS9POx3KrpIiYSQ==} + '@unocss/preset-uno@66.4.2': + resolution: {integrity: sha512-1MFtPivGcpqRQFWdjtP40Enop1y3XDb3tlZXoMQUX0IGLG8HJOT+lfQx/Xl9t73ShJ8aAJ/l6qTxC43ZGNACzA==} - '@unocss/preset-web-fonts@66.0.0': - resolution: {integrity: sha512-9MzfDc6AJILN4Kq7Z91FfFbizBOYgw3lJd2UwqIs3PDYWG5iH5Zv5zhx6jelZVqEW5uWcIARYEEg2m4stZO1ZA==} + '@unocss/preset-web-fonts@66.4.2': + resolution: {integrity: sha512-4FYmleeRoM8r2DqGl6dfIjnX57tepcfZCvVfeCqYnk7475Yddmv1OYkoMjkWMnkK9MzdSxsFwHMU6CIUTmFTzQ==} - '@unocss/preset-wind3@66.0.0': - resolution: {integrity: sha512-WAGRmpi1sb2skvYn9DBQUvhfqrJ+VmQmn5ZGsT2ewvsk7HFCvVLAMzZeKrrTQepeNBRhg6HzFDDi8yg6yB5c9g==} + '@unocss/preset-wind3@66.4.2': + resolution: {integrity: sha512-0Aye/PaT08M/cQhPnGKn93iEVoRJbym0/1eomMvXoL+8oc7DVry35ws06r5CLu5h1sXI6UmS6sejoePFlSkLJQ==} - '@unocss/preset-wind@66.0.0': - resolution: {integrity: sha512-FtvGpHnGC7FiyKJavPnn5y9lsaoWRhXlujCqlT5Bw63kKhMNr0ogKySBpenUhJOhWhVM0OQXn2nZ3GZRxW2qpw==} + '@unocss/preset-wind4@66.4.2': + resolution: {integrity: sha512-F4RZsDqIpnSevD9hY353+Tw5gxpJuHA5HwdKjLnC/TnT9VKKVmV7qUEZ6M0jEuAk1kz2x3/ngnQ9Ftw+C2L84A==} + + '@unocss/preset-wind@66.4.2': + resolution: {integrity: sha512-z/rFYFINNqmBtl3Dh+7UCKpPnPkxM7IIUGszMnvdntky9uhLauJ11dt/Puir73sM2cAfywfgvnHyZ00m0pg7rA==} '@unocss/reset@66.0.0': resolution: {integrity: sha512-YLFz/5yT7mFJC8JSmIUA5+bS3CBCJbtztOw+8rWzjQr/BEVSGuihWUUpI2Df6VVxXIXxKanZR6mIl59yvf+GEA==} - '@unocss/rule-utils@66.0.0': - resolution: {integrity: sha512-UJ51YHbwxYTGyj35ugsPlOT4gaa7tCbXdywZ3m5Nn0JgywwIqGmBFyiN9ZjHBHfJuDxmmPd6lxojoBscih/WMQ==} + '@unocss/reset@66.4.2': + resolution: {integrity: sha512-s3Kq4Q6a/d3/jYe6HTCfXUx7zYAYufetId5n66DZHzQxpeu6CoBS83+b37STTKsw27SOgV28cPJlJtZ6/D6Bhw==} + + '@unocss/rule-utils@66.4.2': + resolution: {integrity: sha512-7z3IuajwXhy2cx3E0IGOFXIiuKC79/jzm4Tt56TC68nXLh/etlH0fKhxVwkZ/HbcQRpVwWyDRNcbh29pmA3DwQ==} engines: {node: '>=14'} - '@unocss/transformer-attributify-jsx@66.0.0': - resolution: {integrity: sha512-jS7szFXXC6RjTv9wo0NACskf618w981bkbyQ5izRO7Ha47sNpHhHDpaltnG7SR9qV4cCtGalOw4onVMHsRKwRg==} + '@unocss/transformer-attributify-jsx@66.4.2': + resolution: {integrity: sha512-de6LzoyW1tkdOftlCrj6z8wEb4j6l1sqmOU1nYKkYHw7luLFGxRUELC7iujlI9KmylbM02bcKfLETAfJy/je2w==} - '@unocss/transformer-compile-class@66.0.0': - resolution: {integrity: sha512-ytUIE0nAcHRMACuTXkHp8auZ483DXrOZw99jk3FJ+aFjpD/pVSFmX14AWJ7bqPFObxb4SLFs6KhQma30ESC22A==} + '@unocss/transformer-compile-class@66.4.2': + resolution: {integrity: sha512-+oiIrV8c3T7qiJdICr6YsEWik5sjbWirXF0mlpcBvZu2HyV559hvHjzuWKr/fl7xYYZKDL9FvddbqWo3DOXh3Q==} - '@unocss/transformer-directives@66.0.0': - resolution: {integrity: sha512-utcg7m2Foi7uHrU5WHadNuJ0a3qWG8tZNkQMi+m0DQpX6KWfuDtDn0zDZ1X+z5lmiB3WGSJERRrsvZbj1q50Mw==} + '@unocss/transformer-directives@66.4.2': + resolution: {integrity: sha512-7m/dTrCUkBkZeSRKPxPEo65Rav239orQSLq6sztwZhoA4x/6H8r58xCkAK0qC9VEalyerpCpyarU3sKN4+ehNg==} - '@unocss/transformer-variant-group@66.0.0': - resolution: {integrity: sha512-1BLjNWtAnR1JAcQGw0TS+nGrVoB9aznzvVZRoTx23dtRr3btvgKPHb8LrD48eD/p8Dtw9j3WfuxMDKXKegKDLg==} + '@unocss/transformer-variant-group@66.4.2': + resolution: {integrity: sha512-SbPDbZUrhQyL4CpvnpvUfrr1DFq8AKf8ofPGbMJDm5S2TInQ34vFaIrhNroGR0szntMZRH5Zlkq6LtVUKDRs5g==} - '@unocss/vite@66.0.0': - resolution: {integrity: sha512-IVcPX8xL+2edyXKt4tp9yu5A6gcbPVCsspfcL0XgziCr01kS+4qSoZ90F3IUs3hXc/AyO5eCpRtGFMPLpOjXQg==} + '@unocss/vite@66.4.2': + resolution: {integrity: sha512-7eON9iPF3qWzuI+M6u0kq7K3y9nEbimZlLj01nGoqrgSGxEsyJpP01QQQsmT7FPRiZzRMJv7BiKMEyDQSuRRCA==} peerDependencies: - vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -4779,12 +4788,15 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + connect-history-api-fallback@2.0.0: resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} engines: {node: '>=0.8'} - consola@3.4.0: - resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} console.table@0.10.0: @@ -5852,6 +5864,9 @@ packages: resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} engines: {node: '>= 18'} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -7276,6 +7291,10 @@ packages: resolution: {integrity: sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==} engines: {node: '>=14'} + local-pkg@1.1.1: + resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} + engines: {node: '>=14'} + locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -8040,6 +8059,9 @@ packages: package-manager-detector@0.2.9: resolution: {integrity: sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==} + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -8223,6 +8245,9 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pkg-types@2.2.0: + resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==} + plist@3.1.0: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} @@ -8405,6 +8430,9 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} + quansync@0.2.10: + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -8996,6 +9024,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} @@ -9327,6 +9356,9 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyglobby@0.2.12: resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} @@ -9547,8 +9579,8 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - unconfig@7.0.0: - resolution: {integrity: sha512-G5CJSoG6ZTxgzCJblEfgpdRK2tos9+UdD2WtecDUVfImzQ0hFjwpH5RVvGMhP4pRpC9ML7NrC4qBsBl0Ttj35A==} + unconfig@7.3.2: + resolution: {integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==} underscore@1.1.7: resolution: {integrity: sha512-w4QtCHoLBXw1mjofIDoMyexaEdWGMedWNDhlWTtT1V1lCRqi65Pnoygkh6+WRdr+Bm8ldkBNkNeCsXGMlQS9HQ==} @@ -9625,12 +9657,12 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - unocss@66.0.0: - resolution: {integrity: sha512-SHstiv1s7zGPSjzOsADzlwRhQM+6817+OqQE3Fv+N/nn2QLNx1bi3WXybFfz5tWkzBtyTZlwdPmeecsIs1yOCA==} + unocss@66.4.2: + resolution: {integrity: sha512-PsZ+4XF/ekiParR7PZEM7AchvHJ78EIfOXlqTPflTOXCYgZ77kG9NaIaIf4lHRevY+rRTyrHrjxdg1Ern2j8qw==} engines: {node: '>=14'} peerDependencies: - '@unocss/webpack': 66.0.0 - vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 + '@unocss/webpack': 66.4.2 + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 peerDependenciesMeta: '@unocss/webpack': optional: true @@ -9974,10 +10006,8 @@ packages: vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - vue-flow-layout@0.1.1: - resolution: {integrity: sha512-JdgRRUVrN0Y2GosA0M68DEbKlXMqJ7FQgsK8CjQD2vxvNSqAU6PZEpi4cfcTVtfM2GVOMjHo7GKKLbXxOBqDqA==} - peerDependencies: - vue: ^3.4.37 + vue-flow-layout@0.2.0: + resolution: {integrity: sha512-zKgsWWkXq0xrus7H4Mc+uFs1ESrmdTXlO0YNbR6wMdPaFvosL3fMB8N7uTV308UhGy9UvTrGhIY7mVz9eN+L0Q==} vue@3.5.13: resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} @@ -10498,12 +10528,12 @@ snapshots: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@antfu/install-pkg@1.0.0': + '@antfu/install-pkg@1.1.0': dependencies: - package-manager-detector: 0.2.9 - tinyexec: 0.3.2 + package-manager-detector: 1.3.0 + tinyexec: 1.0.1 - '@antfu/utils@8.1.1': {} + '@antfu/utils@9.2.0': {} '@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)': dependencies: @@ -10856,7 +10886,7 @@ snapshots: '@babel/generator@7.27.1': dependencies: - '@babel/parser': 7.27.2 + '@babel/parser': 7.28.0 '@babel/types': 7.27.1 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 @@ -10955,7 +10985,7 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.27.1 + '@babel/traverse': 7.28.0 '@babel/types': 7.27.1 transitivePeerDependencies: - supports-color @@ -10965,7 +10995,7 @@ snapshots: '@babel/core': 7.27.1 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.27.1 + '@babel/traverse': 7.28.0 transitivePeerDependencies: - supports-color @@ -12080,14 +12110,14 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.27.2 + '@babel/parser': 7.28.0 '@babel/types': 7.27.1 '@babel/traverse@7.27.1': dependencies: '@babel/code-frame': 7.27.1 '@babel/generator': 7.27.1 - '@babel/parser': 7.27.2 + '@babel/parser': 7.28.0 '@babel/template': 7.27.2 '@babel/types': 7.27.1 debug: 4.4.1(supports-color@8.1.1) @@ -12673,7 +12703,7 @@ snapshots: '@babel/preset-env': 7.27.2(@babel/core@7.27.1) babel-loader: 9.2.1(@babel/core@7.27.1)(webpack@5.95.0(esbuild@0.25.0)) bluebird: 3.7.1 - debug: 4.4.0 + debug: 4.4.1(supports-color@8.1.1) lodash: 4.17.21 webpack: 5.95.0(esbuild@0.25.0) transitivePeerDependencies: @@ -13111,15 +13141,15 @@ snapshots: '@iconify/types@2.0.0': {} - '@iconify/utils@2.3.0': + '@iconify/utils@3.0.1': dependencies: - '@antfu/install-pkg': 1.0.0 - '@antfu/utils': 8.1.1 + '@antfu/install-pkg': 1.1.0 + '@antfu/utils': 9.2.0 '@iconify/types': 2.0.0 - debug: 4.4.0 + debug: 4.4.1(supports-color@8.1.1) globals: 15.15.0 kolorist: 1.8.0 - local-pkg: 1.0.0 + local-pkg: 1.1.1 mlly: 1.7.4 transitivePeerDependencies: - supports-color @@ -13501,6 +13531,10 @@ snapshots: '@polka/url@1.0.0-next.28': {} + '@quansync/fs@0.1.4': + dependencies: + quansync: 0.2.10 + '@react-aria/focus@3.21.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@react-aria/interactions': 3.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -13840,7 +13874,7 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.27.2 + '@babel/parser': 7.28.0 '@babel/types': 7.27.1 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 @@ -13852,7 +13886,7 @@ snapshots: '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.27.2 + '@babel/parser': 7.28.0 '@babel/types': 7.27.1 '@types/babel__traverse@7.20.6': @@ -14358,150 +14392,157 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@unocss/astro@66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3))': + '@unocss/astro@66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))': dependencies: - '@unocss/core': 66.0.0 - '@unocss/reset': 66.0.0 - '@unocss/vite': 66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3)) + '@unocss/core': 66.4.2 + '@unocss/reset': 66.4.2 + '@unocss/vite': 66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)) optionalDependencies: vite: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0) - transitivePeerDependencies: - - vue - '@unocss/cli@66.0.0': + '@unocss/cli@66.4.2': dependencies: '@ampproject/remapping': 2.3.0 - '@unocss/config': 66.0.0 - '@unocss/core': 66.0.0 - '@unocss/preset-uno': 66.0.0 + '@unocss/config': 66.4.2 + '@unocss/core': 66.4.2 + '@unocss/preset-uno': 66.4.2 cac: 6.7.14 chokidar: 3.6.0 colorette: 2.0.20 - consola: 3.4.0 + consola: 3.4.2 magic-string: 0.30.17 pathe: 2.0.3 perfect-debounce: 1.0.0 - tinyglobby: 0.2.12 + tinyglobby: 0.2.14 unplugin-utils: 0.2.4 - '@unocss/config@66.0.0': + '@unocss/config@66.4.2': dependencies: - '@unocss/core': 66.0.0 - unconfig: 7.0.0 + '@unocss/core': 66.4.2 + unconfig: 7.3.2 - '@unocss/core@66.0.0': {} + '@unocss/core@66.4.2': {} - '@unocss/extractor-arbitrary-variants@66.0.0': + '@unocss/extractor-arbitrary-variants@66.4.2': dependencies: - '@unocss/core': 66.0.0 + '@unocss/core': 66.4.2 - '@unocss/inspector@66.0.0(vue@3.5.13(typescript@5.7.3))': + '@unocss/inspector@66.4.2': dependencies: - '@unocss/core': 66.0.0 - '@unocss/rule-utils': 66.0.0 + '@unocss/core': 66.4.2 + '@unocss/rule-utils': 66.4.2 colorette: 2.0.20 gzip-size: 6.0.0 sirv: 3.0.1 - vue-flow-layout: 0.1.1(vue@3.5.13(typescript@5.7.3)) - transitivePeerDependencies: - - vue + vue-flow-layout: 0.2.0 - '@unocss/postcss@66.0.0(postcss@8.5.6)': + '@unocss/postcss@66.4.2(postcss@8.5.6)': dependencies: - '@unocss/config': 66.0.0 - '@unocss/core': 66.0.0 - '@unocss/rule-utils': 66.0.0 + '@unocss/config': 66.4.2 + '@unocss/core': 66.4.2 + '@unocss/rule-utils': 66.4.2 css-tree: 3.1.0 postcss: 8.5.6 - tinyglobby: 0.2.12 + tinyglobby: 0.2.14 - '@unocss/preset-attributify@66.0.0': + '@unocss/preset-attributify@66.4.2': dependencies: - '@unocss/core': 66.0.0 + '@unocss/core': 66.4.2 - '@unocss/preset-icons@66.0.0': + '@unocss/preset-icons@66.4.2': dependencies: - '@iconify/utils': 2.3.0 - '@unocss/core': 66.0.0 + '@iconify/utils': 3.0.1 + '@unocss/core': 66.4.2 ofetch: 1.4.1 transitivePeerDependencies: - supports-color - '@unocss/preset-mini@66.0.0': + '@unocss/preset-mini@66.4.2': dependencies: - '@unocss/core': 66.0.0 - '@unocss/extractor-arbitrary-variants': 66.0.0 - '@unocss/rule-utils': 66.0.0 + '@unocss/core': 66.4.2 + '@unocss/extractor-arbitrary-variants': 66.4.2 + '@unocss/rule-utils': 66.4.2 - '@unocss/preset-tagify@66.0.0': + '@unocss/preset-tagify@66.4.2': dependencies: - '@unocss/core': 66.0.0 + '@unocss/core': 66.4.2 - '@unocss/preset-typography@66.0.0': + '@unocss/preset-typography@66.4.2': dependencies: - '@unocss/core': 66.0.0 - '@unocss/preset-mini': 66.0.0 - '@unocss/rule-utils': 66.0.0 + '@unocss/core': 66.4.2 + '@unocss/preset-mini': 66.4.2 + '@unocss/rule-utils': 66.4.2 - '@unocss/preset-uno@66.0.0': + '@unocss/preset-uno@66.4.2': dependencies: - '@unocss/core': 66.0.0 - '@unocss/preset-wind3': 66.0.0 + '@unocss/core': 66.4.2 + '@unocss/preset-wind3': 66.4.2 - '@unocss/preset-web-fonts@66.0.0': + '@unocss/preset-web-fonts@66.4.2': dependencies: - '@unocss/core': 66.0.0 + '@unocss/core': 66.4.2 ofetch: 1.4.1 - '@unocss/preset-wind3@66.0.0': + '@unocss/preset-wind3@66.4.2': dependencies: - '@unocss/core': 66.0.0 - '@unocss/preset-mini': 66.0.0 - '@unocss/rule-utils': 66.0.0 + '@unocss/core': 66.4.2 + '@unocss/preset-mini': 66.4.2 + '@unocss/rule-utils': 66.4.2 - '@unocss/preset-wind@66.0.0': + '@unocss/preset-wind4@66.4.2': dependencies: - '@unocss/core': 66.0.0 - '@unocss/preset-wind3': 66.0.0 + '@unocss/core': 66.4.2 + '@unocss/extractor-arbitrary-variants': 66.4.2 + '@unocss/rule-utils': 66.4.2 + + '@unocss/preset-wind@66.4.2': + dependencies: + '@unocss/core': 66.4.2 + '@unocss/preset-wind3': 66.4.2 '@unocss/reset@66.0.0': {} - '@unocss/rule-utils@66.0.0': + '@unocss/reset@66.4.2': {} + + '@unocss/rule-utils@66.4.2': dependencies: - '@unocss/core': 66.0.0 + '@unocss/core': 66.4.2 magic-string: 0.30.17 - '@unocss/transformer-attributify-jsx@66.0.0': + '@unocss/transformer-attributify-jsx@66.4.2': dependencies: - '@unocss/core': 66.0.0 + '@babel/parser': 7.28.0 + '@babel/traverse': 7.28.0 + '@unocss/core': 66.4.2 + transitivePeerDependencies: + - supports-color - '@unocss/transformer-compile-class@66.0.0': + '@unocss/transformer-compile-class@66.4.2': dependencies: - '@unocss/core': 66.0.0 + '@unocss/core': 66.4.2 - '@unocss/transformer-directives@66.0.0': + '@unocss/transformer-directives@66.4.2': dependencies: - '@unocss/core': 66.0.0 - '@unocss/rule-utils': 66.0.0 + '@unocss/core': 66.4.2 + '@unocss/rule-utils': 66.4.2 css-tree: 3.1.0 - '@unocss/transformer-variant-group@66.0.0': + '@unocss/transformer-variant-group@66.4.2': dependencies: - '@unocss/core': 66.0.0 + '@unocss/core': 66.4.2 - '@unocss/vite@66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3))': + '@unocss/vite@66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))': dependencies: '@ampproject/remapping': 2.3.0 - '@unocss/config': 66.0.0 - '@unocss/core': 66.0.0 - '@unocss/inspector': 66.0.0(vue@3.5.13(typescript@5.7.3)) + '@unocss/config': 66.4.2 + '@unocss/core': 66.4.2 + '@unocss/inspector': 66.4.2 chokidar: 3.6.0 magic-string: 0.30.17 - tinyglobby: 0.2.12 + pathe: 2.0.3 + tinyglobby: 0.2.14 unplugin-utils: 0.2.4 vite: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0) - transitivePeerDependencies: - - vue '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -15771,9 +15812,11 @@ snapshots: confbox@0.1.8: {} + confbox@0.2.2: {} + connect-history-api-fallback@2.0.0: {} - consola@3.4.0: {} + consola@3.4.2: {} console.table@0.10.0: dependencies: @@ -17197,6 +17240,8 @@ snapshots: transitivePeerDependencies: - supports-color + exsolve@1.0.7: {} + extend@3.0.2: {} extendable-error@0.1.7: {} @@ -17402,7 +17447,7 @@ snapshots: '@actions/core': 1.11.1 arg: 5.0.2 console.table: 0.10.0 - debug: 4.4.0 + debug: 4.4.1(supports-color@8.1.1) find-test-names: 1.29.5(@babel/core@7.27.1) globby: 11.1.0 minimatch: 3.1.2 @@ -18303,7 +18348,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.0 + debug: 4.4.1(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -18936,6 +18981,12 @@ snapshots: mlly: 1.7.4 pkg-types: 1.3.1 + local-pkg@1.1.1: + dependencies: + mlly: 1.7.4 + pkg-types: 2.2.0 + quansync: 0.2.10 + locate-path@3.0.0: dependencies: p-locate: 3.0.0 @@ -19615,7 +19666,7 @@ snapshots: node-source-walk@7.0.0: dependencies: - '@babel/parser': 7.27.2 + '@babel/parser': 7.28.0 nomnom@1.5.2: dependencies: @@ -19873,6 +19924,8 @@ snapshots: package-manager-detector@0.2.9: {} + package-manager-detector@1.3.0: {} + pako@1.0.11: {} pako@2.1.0: {} @@ -20047,6 +20100,12 @@ snapshots: mlly: 1.7.4 pathe: 2.0.3 + pkg-types@2.2.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + plist@3.1.0: dependencies: '@xmldom/xmldom': 0.8.10 @@ -20228,6 +20287,8 @@ snapshots: dependencies: side-channel: 1.1.0 + quansync@0.2.10: {} + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -20580,7 +20641,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.40.2 fsevents: 2.3.3 - roughjs@4.6.6(patch_hash=3543d47108cb41b68ec6a671c0e1f9d0cfe2ce524fea5b0992511ae84c3c6b64): + roughjs@4.6.6(patch_hash=vxb6t6fqvzyhwhtjiliqr25jyq): dependencies: hachure-fill: 0.5.2 path-data-parser: 0.1.0 @@ -21017,7 +21078,7 @@ snapshots: spdy@4.0.2: dependencies: - debug: 4.4.0 + debug: 4.4.1(supports-color@8.1.1) handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -21397,6 +21458,8 @@ snapshots: tinyexec@0.3.2: {} + tinyexec@1.0.1: {} + tinyglobby@0.2.12: dependencies: fdir: 6.4.3(picomatch@4.0.2) @@ -21599,11 +21662,12 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - unconfig@7.0.0: + unconfig@7.3.2: dependencies: - '@antfu/utils': 8.1.1 + '@quansync/fs': 0.1.4 defu: 6.1.4 jiti: 2.4.2 + quansync: 0.2.10 underscore@1.1.7: {} @@ -21689,32 +21753,32 @@ snapshots: universalify@2.0.1: {} - unocss@66.0.0(postcss@8.5.6)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3)): + unocss@66.4.2(postcss@8.5.6)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)): dependencies: - '@unocss/astro': 66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3)) - '@unocss/cli': 66.0.0 - '@unocss/core': 66.0.0 - '@unocss/postcss': 66.0.0(postcss@8.5.6) - '@unocss/preset-attributify': 66.0.0 - '@unocss/preset-icons': 66.0.0 - '@unocss/preset-mini': 66.0.0 - '@unocss/preset-tagify': 66.0.0 - '@unocss/preset-typography': 66.0.0 - '@unocss/preset-uno': 66.0.0 - '@unocss/preset-web-fonts': 66.0.0 - '@unocss/preset-wind': 66.0.0 - '@unocss/preset-wind3': 66.0.0 - '@unocss/transformer-attributify-jsx': 66.0.0 - '@unocss/transformer-compile-class': 66.0.0 - '@unocss/transformer-directives': 66.0.0 - '@unocss/transformer-variant-group': 66.0.0 - '@unocss/vite': 66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3)) + '@unocss/astro': 66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)) + '@unocss/cli': 66.4.2 + '@unocss/core': 66.4.2 + '@unocss/postcss': 66.4.2(postcss@8.5.6) + '@unocss/preset-attributify': 66.4.2 + '@unocss/preset-icons': 66.4.2 + '@unocss/preset-mini': 66.4.2 + '@unocss/preset-tagify': 66.4.2 + '@unocss/preset-typography': 66.4.2 + '@unocss/preset-uno': 66.4.2 + '@unocss/preset-web-fonts': 66.4.2 + '@unocss/preset-wind': 66.4.2 + '@unocss/preset-wind3': 66.4.2 + '@unocss/preset-wind4': 66.4.2 + '@unocss/transformer-attributify-jsx': 66.4.2 + '@unocss/transformer-compile-class': 66.4.2 + '@unocss/transformer-directives': 66.4.2 + '@unocss/transformer-variant-group': 66.4.2 + '@unocss/vite': 66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)) optionalDependencies: vite: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0) transitivePeerDependencies: - postcss - supports-color - - vue unpipe@1.0.0: {} @@ -22051,9 +22115,7 @@ snapshots: vscode-uri@3.1.0: {} - vue-flow-layout@0.1.1(vue@3.5.13(typescript@5.7.3)): - dependencies: - vue: 3.5.13(typescript@5.7.3) + vue-flow-layout@0.2.0: {} vue@3.5.13(typescript@5.7.3): dependencies: From f7a0844a31b32ed7b3aef3e11518fc2cd9d82927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Sun, 17 Aug 2025 19:51:10 +0800 Subject: [PATCH 23/46] fix(pnpm-lock): update roughjs patch hash to correct value --- pnpm-lock.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33637a788..8c9772325 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: patchedDependencies: roughjs: - hash: vxb6t6fqvzyhwhtjiliqr25jyq + hash: 3543d47108cb41b68ec6a671c0e1f9d0cfe2ce524fea5b0992511ae84c3c6b64 path: patches/roughjs.patch importers: @@ -273,7 +273,7 @@ importers: version: 16.0.0 roughjs: specifier: ^4.6.6 - version: 4.6.6(patch_hash=vxb6t6fqvzyhwhtjiliqr25jyq) + version: 4.6.6(patch_hash=3543d47108cb41b68ec6a671c0e1f9d0cfe2ce524fea5b0992511ae84c3c6b64) stylis: specifier: ^4.3.6 version: 4.3.6 @@ -20641,7 +20641,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.40.2 fsevents: 2.3.3 - roughjs@4.6.6(patch_hash=vxb6t6fqvzyhwhtjiliqr25jyq): + roughjs@4.6.6(patch_hash=3543d47108cb41b68ec6a671c0e1f9d0cfe2ce524fea5b0992511ae84c3c6b64): dependencies: hachure-fill: 0.5.2 path-data-parser: 0.1.0 From 71e09bcaefa402dfa2f7b01bb73ea2e868f9352b Mon Sep 17 00:00:00 2001 From: omkarht Date: Mon, 18 Aug 2025 18:21:41 +0530 Subject: [PATCH 24/46] fix: refactored code and adjusted shape, shape label on-behalf-of: @Mermaid-Chart --- .../mermaid/src/diagrams/sequence/svgDraw.js | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index 95863234d..85566ab35 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -468,20 +468,18 @@ const drawActorTypeCollections = function (elem, actor, conf, isFooter) { rect.width = actor.width; rect.height = actor.height; rect.class = cssclass; - // rect.rx = 3; - // rect.ry = 3; rect.name = actor.name; - // 🔹 DRAW STACKED RECTANGLES + // DRAW STACKED RECTANGLES const offset = 6; const shadowRect = { ...rect, - x: rect.x + offset, - y: rect.y + (isFooter ? +offset : -offset), + x: rect.x + (isFooter ? -offset : -offset), + y: rect.y + (isFooter ? +offset : +offset), class: 'actor', }; - drawRect(g, shadowRect); const rectElem = drawRect(g, rect); // draw main rectangle on top + drawRect(g, shadowRect); actor.rectData = rect; if (actor.properties?.icon) { @@ -496,8 +494,8 @@ const drawActorTypeCollections = function (elem, actor, conf, isFooter) { _drawTextCandidateFunc(conf, hasKatex(actor.description))( actor.description, g, - rect.x, - rect.y, + rect.x - offset, + rect.y + offset, rect.width, rect.height, { class: `actor ${ACTOR_BOX_CLASS}` }, @@ -686,13 +684,13 @@ const drawActorTypeControl = function (elem, actor, conf, isFooter) { .append('defs') .append('marker') .attr('id', 'filled-head-control') - .attr('refX', 15.5) - .attr('refY', 7) + .attr('refX', 11) + .attr('refY', 5.8) .attr('markerWidth', 20) .attr('markerHeight', 28) - .attr('orient', '180') + .attr('orient', '172.5') .append('path') - .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); + .attr('d', 'M 14.4 5.6 L 7.2 10.4 L 8.8 5.6 L 7.2 0.8 Z'); // Draw the base circle actElem @@ -717,7 +715,7 @@ const drawActorTypeControl = function (elem, actor, conf, isFooter) { actor.description, actElem, rect.x, - rect.y + 30, + rect.y + r + (isFooter ? 5 : 10), rect.width, rect.height, { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, @@ -753,7 +751,7 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { rect.class = 'actor'; const cx = actor.x + actor.width / 2; - const cy = actorY + 25; + const cy = actorY + (!isFooter ? 25 : 10); const r = 18; actElem @@ -797,7 +795,7 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { actor.description, actElem, rect.x, - rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY) / 2 + r + 2), + rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY + r - 5) / 2), rect.width, rect.height, { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, @@ -871,7 +869,7 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { rect.x = actor.x; rect.y = actorY; const w = rect.width / 4; - const h = rect.height * 0.8; + const h = rect.width / 4; const rx = w / 2; const ry = rx / (2.5 + w / 50); @@ -896,7 +894,7 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { .attr('class', cssclass); if (!isFooter) { - cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); + cylinderGroup.attr('transform', `translate(${w * 1.5}, ${(rect.height + ry) / 4})`); } else { cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); } @@ -905,7 +903,7 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { actor.description, g, rect.x, - rect.y + (!isFooter ? rect.height / 2 : h / 2 + ry), + rect.y + (!isFooter ? (rect.height + ry) / 2 : (rect.height + h) / 4), rect.width, rect.height, { class: `actor ${ACTOR_BOX_CLASS}` }, @@ -961,8 +959,6 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { rect.width = actor.width; rect.height = actor.height; rect.class = 'actor'; - rect.rx = 3; - rect.ry = 3; actElem .append('line') @@ -984,8 +980,7 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { .append('circle') .attr('cx', actor.x + actor.width / 2) .attr('cy', actorY + 10) - .attr('r', radius) - .attr('width', actor.width); + .attr('r', radius); const bounds = actElem.node().getBBox(); actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); @@ -994,7 +989,7 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { actor.description, actElem, rect.x, - rect.y + (!isFooter ? radius / 2 : radius / 2), + rect.y + (!isFooter ? radius / 2 + 3 : radius / 2 - 4), rect.width, rect.height, { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, @@ -1007,8 +1002,6 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { actElem.attr('transform', `translate(0,${radius / 2 + 7})`); } - // actElem.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`); - return actor.height; }; From d86e46b705c2a1d4c4853fbf953e56dd6e6b44a6 Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 19 Aug 2025 13:44:11 +0530 Subject: [PATCH 25/46] fix: refactored test cases on-behalf-of: @Mermaid-Chart --- .../diagrams/sequence/sequenceDiagram.spec.js | 142 ++++++++++++++++-- 1 file changed, 126 insertions(+), 16 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 61738ae5e..f01ff9ec7 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2058,26 +2058,52 @@ Bob->>Alice:Got it! expect(messages[0].from).toBe('Alice'); expect(messages[0].to).toBe('Bob'); }); - describe('when parsing extended participant syntax ', () => { - it('should parse a message', async () => { - const actor1 = 'database'; + describe('when parsing extended participant syntax', () => { + it('should parse participants with different quote styles and whitespace', async () => { const diagram = await Diagram.fromText(` - sequenceDiagram - participant Alice@{ "type" : "database" } - participant Bob@{ "type" : "database" } - participant Carl@{ type: "database" } - participant David@{ "type" : 'database' } - participant Eve@{ type: 'database' } - participant Favela@{ "type" : "database" } - Bob->>+Alice: Hi Alice - Alice->>+Bob: Hi Bob - `); + sequenceDiagram + participant Alice@{ "type" : "database" } + participant Bob@{ "type" : "database" } + participant Carl@{ type: "database" } + participant David@{ "type" : 'database' } + participant Eve@{ type: 'database' } + participant Favela@{ "type" : "database" } + Bob->>+Alice: Hi Alice + Alice->>+Bob: Hi Bob + `); + const actors = diagram.db.getActors(); + + expect(actors.get('Alice').type).toBe('database'); + expect(actors.get('Alice').description).toBe('Alice'); + + expect(actors.get('Bob').type).toBe('database'); + expect(actors.get('Bob').description).toBe('Bob'); + + expect(actors.get('Carl').type).toBe('database'); + expect(actors.get('Carl').description).toBe('Carl'); + + expect(actors.get('David').type).toBe('database'); + expect(actors.get('David').description).toBe('David'); + + expect(actors.get('Eve').type).toBe('database'); + expect(actors.get('Eve').description).toBe('Eve'); + + expect(actors.get('Favela').type).toBe('database'); + expect(actors.get('Favela').description).toBe('Favela'); + + // Verify messages were parsed correctly const messages = diagram.db.getMessages(); + expect(messages.length).toBe(4); // 2 messages + 2 activation messages + expect(messages[0].from).toBe('Bob'); + expect(messages[0].to).toBe('Alice'); + expect(messages[0].message).toBe('Hi Alice'); + expect(messages[2].from).toBe('Alice'); // Second message (index 2 due to activation) + expect(messages[2].to).toBe('Bob'); + expect(messages[2].message).toBe('Hi Bob'); }); - it('should parse a message', async () => { - const actor1 = 'database'; + it('should parse mixed participant types with extended syntax', async () => { const diagram = await Diagram.fromText(` sequenceDiagram participant lead @@ -2085,9 +2111,93 @@ Bob->>Alice:Got it! API->>+Database: getUserb Database-->>-API: userb dsa --> Database: hello - `); +`); + // Verify actors were created + const actors = diagram.db.getActors(); + + expect(actors.get('lead').type).toBe('participant'); + expect(actors.get('lead').description).toBe('lead'); + + // Participant with extended syntax + expect(actors.get('dsa').type).toBe('queue'); + expect(actors.get('dsa').description).toBe('dsa'); + + // Implicitly created actors (from messages) + expect(actors.get('API').type).toBe('participant'); + expect(actors.get('API').description).toBe('API'); + + expect(actors.get('Database').type).toBe('participant'); + expect(actors.get('Database').description).toBe('Database'); + + // Verify messages were parsed correctly const messages = diagram.db.getMessages(); + expect(messages.length).toBe(5); // 3 messages + 2 activation messages + + // First message with activation + expect(messages[0].from).toBe('API'); + expect(messages[0].to).toBe('Database'); + expect(messages[0].message).toBe('getUserb'); + expect(messages[0].activate).toBe(true); + + // Second message with deactivation + expect(messages[2].from).toBe('Database'); + expect(messages[2].to).toBe('API'); + expect(messages[2].message).toBe('userb'); + + // Third message + expect(messages[4].from).toBe('dsa'); + expect(messages[4].to).toBe('Database'); + expect(messages[4].message).toBe('hello'); + }); + + it('should fail for malformed JSON in participant definition', async () => { + const invalidDiagram = ` + sequenceDiagram + participant D@{ "type: "entity" } + participant E@{ "type": "dat + abase } + `; + + let error = false; + try { + await mermaidAPI.parse(invalidDiagram); + } catch (e) { + error = true; + } + expect(error).toBe(true); + }); + + it('should fail for missing colon separator', async () => { + const invalidDiagram = ` + sequenceDiagram + participant C@{ "type" "control" } + C ->> C: action + `; + + let error = false; + try { + await mermaidAPI.parse(invalidDiagram); + } catch (e) { + error = true; + } + expect(error).toBe(true); + }); + + it('should fail for missing closing brace', async () => { + const invalidDiagram = ` + sequenceDiagram + participant E@{ "type": "entity" + E ->> E: process + `; + + let error = false; + try { + await mermaidAPI.parse(invalidDiagram); + } catch (e) { + error = true; + } + expect(error).toBe(true); }); }); describe('participant type parsing', () => { From d4c76968e97b3049e8975113fb4d9d244f41df43 Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Thu, 21 Aug 2025 19:09:39 +0530 Subject: [PATCH 26/46] fix: correct rendering of bidirectional arrows with autonumber on-behalf-of: @Mermaid-Chart --- .../rendering/sequencediagram.spec.js | 11 +++++++++ .../src/diagrams/sequence/sequenceRenderer.ts | 24 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index f18e99abf..6709b557c 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -893,6 +893,17 @@ describe('Sequence diagram', () => { } ); }); + + it('should handle bidirectional arrows with autonumber', () => { + imgSnapshotTest(` + sequenceDiagram + autonumber + participant A + participant B + A<<->>B: This is a bidirectional message + A->B: This is a normal message`); + }); + it('should support actor links and properties when not mirrored EXPERIMENTAL: USE WITH CAUTION', () => { //Be aware that the syntax for "properties" is likely to be changed. imgSnapshotTest( diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index cfba92b79..2c0d898ca 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -476,7 +476,29 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO // add node number if (sequenceVisible || conf.showSequenceNumbers) { - line.attr('marker-start', 'url(' + url + '#sequencenumber)'); + const isBidirectional = + type === diagObj.db.LINETYPE.BIDIRECTIONAL_SOLID || + type === diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED; + + if (isBidirectional) { + const SEQUENCE_NUMBER_RADIUS = 6; + + if (startx < stopx) { + line.attr('x1', startx + 2 * SEQUENCE_NUMBER_RADIUS); + } else { + line.attr('x1', startx + SEQUENCE_NUMBER_RADIUS); + } + } + + diagram + .append('line') + .attr('x1', startx) + .attr('y1', lineStartY) + .attr('x2', startx) + .attr('y2', lineStartY) + .attr('stroke-width', 0) + .attr('marker-start', 'url(' + url + '#sequencenumber)'); + diagram .append('text') .attr('x', startx) From e0b45c2d2b41c2a9038bf87646fa3ccd7560eb20 Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Thu, 21 Aug 2025 19:15:30 +0530 Subject: [PATCH 27/46] chore: added changeset on-behalf-of: @Mermaid-Chart --- .changeset/crazy-loops-matter.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/crazy-loops-matter.md diff --git a/.changeset/crazy-loops-matter.md b/.changeset/crazy-loops-matter.md new file mode 100644 index 000000000..e6377a9e5 --- /dev/null +++ b/.changeset/crazy-loops-matter.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Handle arrows correctly when auto number is enabled From 2bc5b6d2fa7eb9e5dd75c06046d31e4a361f1369 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 21 Aug 2025 19:26:54 +0530 Subject: [PATCH 28/46] docs(flowchart): fix image node documentation --- docs/syntax/flowchart.md | 16 ++++++++++++++-- packages/mermaid/src/docs/syntax/flowchart.md | 10 ++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md index daaa29581..08c145f6f 100644 --- a/docs/syntax/flowchart.md +++ b/docs/syntax/flowchart.md @@ -983,11 +983,23 @@ flowchart TD - `b` - **w**: The width of the image. If not defined, this will default to the natural width of the image. - **h**: The height of the image. If not defined, this will default to the natural height of the image. -- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the height (`h`) accordingly to the width (`w`). If not defined, this will default to `off` Possible values are: +- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the width (`w`) accordingly to the height (`h`). If not defined, this will default to `off` Possible values are: - `on` - `off` -These new shapes provide additional flexibility and visual appeal to your flowcharts, making them more informative and engaging. +If you want to resize an image, but keep the same aspect ratio, set `h`, and set `constraint: on` to constrain the aspect ratio. E.g. + +```mermaid-example +flowchart TD + %% My image with a constrained aspect ratio + A@{ img: "https://mermaid.js.org/favicon.svg", label: "My example image label", pos: "t", h: 60, constraint: "on" } +``` + +```mermaid +flowchart TD + %% My image with a constrained aspect ratio + A@{ img: "https://mermaid.js.org/favicon.svg", label: "My example image label", pos: "t", h: 60, constraint: "on" } +``` ## Links between nodes diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md index a19dcff21..341143c47 100644 --- a/packages/mermaid/src/docs/syntax/flowchart.md +++ b/packages/mermaid/src/docs/syntax/flowchart.md @@ -590,11 +590,17 @@ flowchart TD - `b` - **w**: The width of the image. If not defined, this will default to the natural width of the image. - **h**: The height of the image. If not defined, this will default to the natural height of the image. -- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the height (`h`) accordingly to the width (`w`). If not defined, this will default to `off` Possible values are: +- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the width (`w`) accordingly to the height (`h`). If not defined, this will default to `off` Possible values are: - `on` - `off` -These new shapes provide additional flexibility and visual appeal to your flowcharts, making them more informative and engaging. +If you want to resize an image, but keep the same aspect ratio, set `h`, and set `constraint: on` to constrain the aspect ratio. E.g. + +```mermaid +flowchart TD + %% My image with a constrained aspect ratio + A@{ img: "https://mermaid.js.org/favicon.svg", label: "My example image label", pos: "t", h: 60, constraint: "on" } +``` ## Links between nodes From e438e035bc5f1a2e061be9a5fb558e9ea1e81fa9 Mon Sep 17 00:00:00 2001 From: omkarht Date: Thu, 21 Aug 2025 19:34:27 +0530 Subject: [PATCH 29/46] chore: added changeset --- .changeset/thick-fans-run.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thick-fans-run.md diff --git a/.changeset/thick-fans-run.md b/.changeset/thick-fans-run.md new file mode 100644 index 000000000..62659b24d --- /dev/null +++ b/.changeset/thick-fans-run.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +docs: fix incorrect image node documentation From 93467a6fce72b8232c1c41e85048a06b8d66b5a6 Mon Sep 17 00:00:00 2001 From: omkarht Date: Mon, 25 Aug 2025 12:58:17 +0530 Subject: [PATCH 30/46] fix: removed changeset on-behalf-of: @Mermaid-Chart --- .changeset/thick-fans-run.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/thick-fans-run.md diff --git a/.changeset/thick-fans-run.md b/.changeset/thick-fans-run.md deleted file mode 100644 index 62659b24d..000000000 --- a/.changeset/thick-fans-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'mermaid': patch ---- - -docs: fix incorrect image node documentation From 8322a6359847e3b7a65391d5d33f9955bf4685ae Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Mon, 25 Aug 2025 16:17:04 +0530 Subject: [PATCH 31/46] feat: add helper to differentiate user-defined layout from default on-behalf-of: @Mermaid-Chart --- docs/config/setup/config/README.md | 1 + .../setup/config/functions/getLayoutInfo.md | 27 +++++++++++++++++++ packages/mermaid/src/config.ts | 16 +++++++++++ 3 files changed, 44 insertions(+) create mode 100644 docs/config/setup/config/functions/getLayoutInfo.md diff --git a/docs/config/setup/config/README.md b/docs/config/setup/config/README.md index 67fca78eb..6b17a38ad 100644 --- a/docs/config/setup/config/README.md +++ b/docs/config/setup/config/README.md @@ -18,6 +18,7 @@ - [addDirective](functions/addDirective.md) - [getConfig](functions/getConfig.md) +- [getLayoutInfo](functions/getLayoutInfo.md) - [getSiteConfig](functions/getSiteConfig.md) - [reset](functions/reset.md) - [sanitize](functions/sanitize.md) diff --git a/docs/config/setup/config/functions/getLayoutInfo.md b/docs/config/setup/config/functions/getLayoutInfo.md new file mode 100644 index 000000000..044c6f96b --- /dev/null +++ b/docs/config/setup/config/functions/getLayoutInfo.md @@ -0,0 +1,27 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/config/functions/getLayoutInfo.md](../../../../../packages/mermaid/src/docs/config/setup/config/functions/getLayoutInfo.md). + +[**mermaid**](../../README.md) + +--- + +# Function: getLayoutInfo() + +> **getLayoutInfo**(): `object` + +Defined in: [packages/mermaid/src/config.ts:260](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L260) + +## Returns + +`object` + +### isUserDefined + +> **isUserDefined**: `boolean` + +### layout + +> **layout**: `string` diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts index 9468a3e46..295432ed3 100644 --- a/packages/mermaid/src/config.ts +++ b/packages/mermaid/src/config.ts @@ -248,3 +248,19 @@ const checkConfig = (config: MermaidConfig) => { issueWarning('LAZY_LOAD_DEPRECATED'); } }; + +const isUserDefinedLayout = (): boolean => { + if (configFromInitialize?.layout) { + return true; + } + + return directives.some((d) => !!d.layout); +}; + +export const getLayoutInfo = () => { + const cfg = getConfig(); + return { + layout: cfg.layout ?? 'dagre', + isUserDefined: isUserDefinedLayout(), + }; +}; From b52766653cc1f87ea348a3f010d5b9c3f5b6107d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:22:30 +0000 Subject: [PATCH 32/46] chore(deps): update peter-evans/create-pull-request digest to 18e4695 --- .github/workflows/e2e-timings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-timings.yml b/.github/workflows/e2e-timings.yml index 2bbfa8412..21dbda293 100644 --- a/.github/workflows/e2e-timings.yml +++ b/.github/workflows/e2e-timings.yml @@ -58,7 +58,7 @@ jobs: echo "EOF" >> $GITHUB_OUTPUT - name: Commit and create pull request - uses: peter-evans/create-pull-request@cb4d3bfce175d44325c6b7697f81e0afe8a79bdf + uses: peter-evans/create-pull-request@18e469570b1cf0dfc11d60ec121099f8ff3e617a with: add-paths: | cypress/timings.json From 31ecf31c2e35bbe7e3bec02b0ce50723a8d9ddee Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Mon, 25 Aug 2025 18:26:03 +0530 Subject: [PATCH 33/46] refactor: remove layout-specific checks and create generic function on-behalf-of: @Mermaid-Chart --- docs/config/setup/config/README.md | 2 +- .../setup/config/functions/getLayoutInfo.md | 27 ------------------- .../config/functions/getUserDefinedConfig.md | 19 +++++++++++++ packages/mermaid/src/config.ts | 20 +++++++------- 4 files changed, 29 insertions(+), 39 deletions(-) delete mode 100644 docs/config/setup/config/functions/getLayoutInfo.md create mode 100644 docs/config/setup/config/functions/getUserDefinedConfig.md diff --git a/docs/config/setup/config/README.md b/docs/config/setup/config/README.md index 6b17a38ad..c811c7b08 100644 --- a/docs/config/setup/config/README.md +++ b/docs/config/setup/config/README.md @@ -18,8 +18,8 @@ - [addDirective](functions/addDirective.md) - [getConfig](functions/getConfig.md) -- [getLayoutInfo](functions/getLayoutInfo.md) - [getSiteConfig](functions/getSiteConfig.md) +- [getUserDefinedConfig](functions/getUserDefinedConfig.md) - [reset](functions/reset.md) - [sanitize](functions/sanitize.md) - [saveConfigFromInitialize](functions/saveConfigFromInitialize.md) diff --git a/docs/config/setup/config/functions/getLayoutInfo.md b/docs/config/setup/config/functions/getLayoutInfo.md deleted file mode 100644 index 044c6f96b..000000000 --- a/docs/config/setup/config/functions/getLayoutInfo.md +++ /dev/null @@ -1,27 +0,0 @@ -> **Warning** -> -> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. -> -> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/config/functions/getLayoutInfo.md](../../../../../packages/mermaid/src/docs/config/setup/config/functions/getLayoutInfo.md). - -[**mermaid**](../../README.md) - ---- - -# Function: getLayoutInfo() - -> **getLayoutInfo**(): `object` - -Defined in: [packages/mermaid/src/config.ts:260](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L260) - -## Returns - -`object` - -### isUserDefined - -> **isUserDefined**: `boolean` - -### layout - -> **layout**: `string` diff --git a/docs/config/setup/config/functions/getUserDefinedConfig.md b/docs/config/setup/config/functions/getUserDefinedConfig.md new file mode 100644 index 000000000..ed39f1337 --- /dev/null +++ b/docs/config/setup/config/functions/getUserDefinedConfig.md @@ -0,0 +1,19 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/config/functions/getUserDefinedConfig.md](../../../../../packages/mermaid/src/docs/config/setup/config/functions/getUserDefinedConfig.md). + +[**mermaid**](../../README.md) + +--- + +# Function: getUserDefinedConfig() + +> **getUserDefinedConfig**(): [`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md) + +Defined in: [packages/mermaid/src/config.ts:252](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L252) + +## Returns + +[`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md) diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts index 295432ed3..4fcb3224d 100644 --- a/packages/mermaid/src/config.ts +++ b/packages/mermaid/src/config.ts @@ -249,18 +249,16 @@ const checkConfig = (config: MermaidConfig) => { } }; -const isUserDefinedLayout = (): boolean => { - if (configFromInitialize?.layout) { - return true; +export const getUserDefinedConfig = (): MermaidConfig => { + let userConfig: MermaidConfig = {}; + + if (configFromInitialize) { + userConfig = assignWithDepth(userConfig, configFromInitialize); } - return directives.some((d) => !!d.layout); -}; + for (const d of directives) { + userConfig = assignWithDepth(userConfig, d); + } -export const getLayoutInfo = () => { - const cfg = getConfig(); - return { - layout: cfg.layout ?? 'dagre', - isUserDefined: isUserDefinedLayout(), - }; + return userConfig; }; From 4760ed88937f17d6c7b191fb8f588f364e6195e8 Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Mon, 25 Aug 2025 19:13:58 +0530 Subject: [PATCH 34/46] fix: add unit tests for getUserDefinedConfig across different scenarios on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/config.spec.ts | 163 ++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/packages/mermaid/src/config.spec.ts b/packages/mermaid/src/config.spec.ts index 000be1282..2cacfebbf 100644 --- a/packages/mermaid/src/config.spec.ts +++ b/packages/mermaid/src/config.spec.ts @@ -78,3 +78,166 @@ describe('when working with site config', () => { expect(config_4.altFontFamily).toBeUndefined(); }); }); + +describe('getUserDefinedConfig', () => { + beforeEach(() => { + configApi.reset(); + }); + + it('should return empty object when no user config is defined', () => { + const userConfig = configApi.getUserDefinedConfig(); + expect(userConfig).toEqual({}); + }); + + it('should return config from initialize only', () => { + const initConfig: MermaidConfig = { theme: 'dark', fontFamily: 'Arial' }; + configApi.saveConfigFromInitialize(initConfig); + + const userConfig = configApi.getUserDefinedConfig(); + expect(userConfig).toEqual(initConfig); + }); + + it('should return config from directives only', () => { + const directive1: MermaidConfig = { layout: 'elk', fontSize: 14 }; + const directive2: MermaidConfig = { theme: 'forest' }; + + configApi.addDirective(directive1); + configApi.addDirective(directive2); + + const userConfig = configApi.getUserDefinedConfig(); + expect(userConfig.layout).toBe('elk'); + expect(userConfig.fontSize).toBe(14); + expect(userConfig.theme).toBe('forest'); + }); + + it('should combine initialize config and directives', () => { + const initConfig: MermaidConfig = { theme: 'dark', fontFamily: 'Arial', layout: 'dagre' }; + const directive1: MermaidConfig = { layout: 'elk', fontSize: 14 }; + const directive2: MermaidConfig = { theme: 'forest' }; + + configApi.saveConfigFromInitialize(initConfig); + configApi.addDirective(directive1); + configApi.addDirective(directive2); + + const userConfig = configApi.getUserDefinedConfig(); + expect(userConfig.theme).toBe('forest'); + expect(userConfig.fontFamily).toBe('Arial'); + expect(userConfig.layout).toBe('elk'); + expect(userConfig.fontSize).toBe(14); + }); + + it('should handle nested config objects properly', () => { + const initConfig: MermaidConfig = { + flowchart: { nodeSpacing: 50, rankSpacing: 100 }, + theme: 'default', + }; + const directive: MermaidConfig = { + flowchart: { nodeSpacing: 75, curve: 'basis' }, + mindmap: { padding: 20 }, + }; + + configApi.saveConfigFromInitialize(initConfig); + configApi.addDirective(directive); + + const userConfig = configApi.getUserDefinedConfig(); + expect(userConfig).toEqual({ + theme: 'default', + flowchart: { + nodeSpacing: 75, + rankSpacing: 100, + curve: 'basis', + }, + mindmap: { + padding: 20, + }, + }); + }); + + it('should handle complex nested overrides', () => { + const initConfig: MermaidConfig = { + flowchart: { + nodeSpacing: 50, + rankSpacing: 100, + curve: 'linear', + }, + theme: 'default', + }; + const directive1: MermaidConfig = { + flowchart: { + nodeSpacing: 75, + }, + fontSize: 12, + }; + const directive2: MermaidConfig = { + flowchart: { + curve: 'basis', + nodeSpacing: 100, + }, + mindmap: { + padding: 15, + }, + }; + + configApi.saveConfigFromInitialize(initConfig); + configApi.addDirective(directive1); + configApi.addDirective(directive2); + + const userConfig = configApi.getUserDefinedConfig(); + expect(userConfig).toEqual({ + theme: 'default', + fontSize: 12, + flowchart: { + nodeSpacing: 100, + rankSpacing: 100, + curve: 'basis', + }, + mindmap: { + padding: 15, + }, + }); + }); + + it('should return independent copies (not references)', () => { + const initConfig: MermaidConfig = { theme: 'dark', flowchart: { nodeSpacing: 50 } }; + configApi.saveConfigFromInitialize(initConfig); + + const userConfig1 = configApi.getUserDefinedConfig(); + const userConfig2 = configApi.getUserDefinedConfig(); + + userConfig1.theme = 'neutral'; + userConfig1.flowchart!.nodeSpacing = 999; + + expect(userConfig2.theme).toBe('dark'); + expect(userConfig2.flowchart!.nodeSpacing).toBe(50); + }); + + it('should handle edge cases with undefined values', () => { + const initConfig: MermaidConfig = { theme: 'dark', layout: undefined }; + const directive: MermaidConfig = { fontSize: 14, fontFamily: undefined }; + + configApi.saveConfigFromInitialize(initConfig); + configApi.addDirective(directive); + + const userConfig = configApi.getUserDefinedConfig(); + expect(userConfig.theme).toBe('dark'); + expect(userConfig.layout).toBeUndefined(); + expect(userConfig.fontFamily).toBeUndefined(); + expect(userConfig.fontSize).toBe(14); + }); + + it('should work correctly after reset', () => { + const initConfig: MermaidConfig = { theme: 'dark' }; + const directive: MermaidConfig = { layout: 'elk' }; + + configApi.saveConfigFromInitialize(initConfig); + configApi.addDirective(directive); + + let userConfig = configApi.getUserDefinedConfig(); + expect(userConfig).toEqual({ theme: 'dark', layout: 'elk' }); + + configApi.reset(); + + userConfig = configApi.getUserDefinedConfig(); + expect(userConfig).toEqual({ theme: 'dark' }); + }); +}); From c41e08cb7a576f5cf929586639a57e4ebd35877f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 04:13:51 +0000 Subject: [PATCH 35/46] chore: update E2E timings --- cypress/timings.json | 112 ++++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/cypress/timings.json b/cypress/timings.json index 86d5b5222..5708c3414 100644 --- a/cypress/timings.json +++ b/cypress/timings.json @@ -2,219 +2,223 @@ "durations": [ { "spec": "cypress/integration/other/configuration.spec.js", - "duration": 6297 + "duration": 6162 }, { "spec": "cypress/integration/other/external-diagrams.spec.js", - "duration": 2187 + "duration": 2148 }, { "spec": "cypress/integration/other/ghsa.spec.js", - "duration": 3509 + "duration": 3585 }, { "spec": "cypress/integration/other/iife.spec.js", - "duration": 2218 + "duration": 2099 }, { "spec": "cypress/integration/other/interaction.spec.js", - "duration": 12104 + "duration": 12119 }, { "spec": "cypress/integration/other/rerender.spec.js", - "duration": 2151 + "duration": 2063 }, { "spec": "cypress/integration/other/xss.spec.js", - "duration": 33064 + "duration": 31921 }, { "spec": "cypress/integration/rendering/appli.spec.js", - "duration": 3488 + "duration": 3385 }, { "spec": "cypress/integration/rendering/architecture.spec.ts", - "duration": 106 + "duration": 108 }, { "spec": "cypress/integration/rendering/block.spec.js", - "duration": 18317 + "duration": 18063 }, { "spec": "cypress/integration/rendering/c4.spec.js", - "duration": 5592 + "duration": 5519 }, { "spec": "cypress/integration/rendering/classDiagram-elk-v3.spec.js", - "duration": 39358 + "duration": 40040 }, { "spec": "cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js", - "duration": 37160 + "duration": 38665 }, { "spec": "cypress/integration/rendering/classDiagram-v2.spec.js", - "duration": 23660 + "duration": 22836 }, { "spec": "cypress/integration/rendering/classDiagram-v3.spec.js", - "duration": 36866 + "duration": 37096 }, { "spec": "cypress/integration/rendering/classDiagram.spec.js", - "duration": 17334 + "duration": 16452 }, { "spec": "cypress/integration/rendering/conf-and-directives.spec.js", - "duration": 9871 + "duration": 10387 }, { "spec": "cypress/integration/rendering/current.spec.js", - "duration": 2833 + "duration": 2803 }, { "spec": "cypress/integration/rendering/erDiagram-unified.spec.js", - "duration": 85321 + "duration": 86891 }, { "spec": "cypress/integration/rendering/erDiagram.spec.js", - "duration": 15673 + "duration": 15206 }, { "spec": "cypress/integration/rendering/errorDiagram.spec.js", - "duration": 3724 + "duration": 3540 }, { "spec": "cypress/integration/rendering/flowchart-elk.spec.js", - "duration": 41178 + "duration": 41975 }, { "spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js", - "duration": 29966 + "duration": 30909 }, { "spec": "cypress/integration/rendering/flowchart-icon.spec.js", - "duration": 7689 + "duration": 7881 }, { "spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts", - "duration": 24709 + "duration": 24294 }, { "spec": "cypress/integration/rendering/flowchart-v2.spec.js", - "duration": 45565 + "duration": 47652 }, { "spec": "cypress/integration/rendering/flowchart.spec.js", - "duration": 31144 + "duration": 32049 }, { "spec": "cypress/integration/rendering/gantt.spec.js", - "duration": 20808 + "duration": 20248 }, { "spec": "cypress/integration/rendering/gitGraph.spec.js", - "duration": 49985 + "duration": 51202 }, { "spec": "cypress/integration/rendering/iconShape.spec.ts", - "duration": 273272 + "duration": 283546 }, { "spec": "cypress/integration/rendering/imageShape.spec.ts", - "duration": 55880 + "duration": 57257 }, { "spec": "cypress/integration/rendering/info.spec.ts", - "duration": 3271 + "duration": 3352 }, { "spec": "cypress/integration/rendering/journey.spec.js", - "duration": 7293 + "duration": 7423 }, { "spec": "cypress/integration/rendering/kanban.spec.ts", - "duration": 7861 + "duration": 7804 }, { "spec": "cypress/integration/rendering/katex.spec.js", - "duration": 3922 + "duration": 3847 }, { "spec": "cypress/integration/rendering/marker_unique_id.spec.js", - "duration": 2726 + "duration": 2637 }, { "spec": "cypress/integration/rendering/mindmap.spec.ts", - "duration": 11670 + "duration": 11658 }, { "spec": "cypress/integration/rendering/newShapes.spec.ts", - "duration": 146020 + "duration": 149500 }, { "spec": "cypress/integration/rendering/oldShapes.spec.ts", - "duration": 114244 + "duration": 115427 }, { "spec": "cypress/integration/rendering/packet.spec.ts", - "duration": 5036 + "duration": 4801 }, { "spec": "cypress/integration/rendering/pie.spec.ts", - "duration": 6545 + "duration": 6786 }, { "spec": "cypress/integration/rendering/quadrantChart.spec.js", - "duration": 9097 + "duration": 9422 }, { "spec": "cypress/integration/rendering/radar.spec.js", - "duration": 5676 + "duration": 5652 }, { "spec": "cypress/integration/rendering/requirement.spec.js", - "duration": 2795 + "duration": 2787 }, { "spec": "cypress/integration/rendering/requirementDiagram-unified.spec.js", - "duration": 51660 + "duration": 53631 }, { "spec": "cypress/integration/rendering/sankey.spec.ts", - "duration": 6957 + "duration": 7075 + }, + { + "spec": "cypress/integration/rendering/sequencediagram-v2.spec.js", + "duration": 20446 }, { "spec": "cypress/integration/rendering/sequencediagram.spec.js", - "duration": 36026 + "duration": 37326 }, { "spec": "cypress/integration/rendering/stateDiagram-v2.spec.js", - "duration": 29551 + "duration": 29208 }, { "spec": "cypress/integration/rendering/stateDiagram.spec.js", - "duration": 17364 + "duration": 16328 }, { "spec": "cypress/integration/rendering/theme.spec.js", - "duration": 30209 + "duration": 30541 }, { "spec": "cypress/integration/rendering/timeline.spec.ts", - "duration": 8699 + "duration": 8611 }, { "spec": "cypress/integration/rendering/treemap.spec.ts", - "duration": 12168 + "duration": 11878 }, { "spec": "cypress/integration/rendering/xyChart.spec.js", - "duration": 21453 + "duration": 20400 }, { "spec": "cypress/integration/rendering/zenuml.spec.js", - "duration": 3577 + "duration": 3528 } ] } From 50127f3ffe3d8a9e66cf263e1159b72785e848ee Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Tue, 26 Aug 2025 13:17:38 +0530 Subject: [PATCH 36/46] fix: review comments related to getUserDefinedConfig tests on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/config.spec.ts | 109 +++++++++++++++++----------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/packages/mermaid/src/config.spec.ts b/packages/mermaid/src/config.spec.ts index 2cacfebbf..7fbae03af 100644 --- a/packages/mermaid/src/config.spec.ts +++ b/packages/mermaid/src/config.spec.ts @@ -104,10 +104,14 @@ describe('getUserDefinedConfig', () => { configApi.addDirective(directive1); configApi.addDirective(directive2); - const userConfig = configApi.getUserDefinedConfig(); - expect(userConfig.layout).toBe('elk'); - expect(userConfig.fontSize).toBe(14); - expect(userConfig.theme).toBe('forest'); + expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(` + { + "fontFamily": "Arial", + "fontSize": 14, + "layout": "elk", + "theme": "forest", + } + `); }); it('should combine initialize config and directives', () => { @@ -120,10 +124,14 @@ describe('getUserDefinedConfig', () => { configApi.addDirective(directive2); const userConfig = configApi.getUserDefinedConfig(); - expect(userConfig.theme).toBe('forest'); - expect(userConfig.fontFamily).toBe('Arial'); - expect(userConfig.layout).toBe('elk'); - expect(userConfig.fontSize).toBe(14); + expect(userConfig).toMatchInlineSnapshot(` + { + "fontFamily": "Arial", + "fontSize": 14, + "layout": "elk", + "theme": "forest", + } + `); }); it('should handle nested config objects properly', () => { @@ -140,17 +148,19 @@ describe('getUserDefinedConfig', () => { configApi.addDirective(directive); const userConfig = configApi.getUserDefinedConfig(); - expect(userConfig).toEqual({ - theme: 'default', - flowchart: { - nodeSpacing: 75, - rankSpacing: 100, - curve: 'basis', - }, - mindmap: { - padding: 20, - }, - }); + expect(userConfig).toMatchInlineSnapshot(` + { + "flowchart": { + "curve": "basis", + "nodeSpacing": 75, + "rankSpacing": 100, + }, + "mindmap": { + "padding": 20, + }, + "theme": "default", + } + `); }); it('should handle complex nested overrides', () => { @@ -183,18 +193,20 @@ describe('getUserDefinedConfig', () => { configApi.addDirective(directive2); const userConfig = configApi.getUserDefinedConfig(); - expect(userConfig).toEqual({ - theme: 'default', - fontSize: 12, - flowchart: { - nodeSpacing: 100, - rankSpacing: 100, - curve: 'basis', - }, - mindmap: { - padding: 15, - }, - }); + expect(userConfig).toMatchInlineSnapshot(` + { + "flowchart": { + "curve": "basis", + "nodeSpacing": 100, + "rankSpacing": 100, + }, + "fontSize": 12, + "mindmap": { + "padding": 15, + }, + "theme": "default", + } + `); }); it('should return independent copies (not references)', () => { @@ -207,8 +219,14 @@ describe('getUserDefinedConfig', () => { userConfig1.theme = 'neutral'; userConfig1.flowchart!.nodeSpacing = 999; - expect(userConfig2.theme).toBe('dark'); - expect(userConfig2.flowchart!.nodeSpacing).toBe(50); + expect(userConfig2).toMatchInlineSnapshot(` + { + "flowchart": { + "nodeSpacing": 50, + }, + "theme": "dark", + } + `); }); it('should handle edge cases with undefined values', () => { @@ -218,26 +236,29 @@ describe('getUserDefinedConfig', () => { configApi.saveConfigFromInitialize(initConfig); configApi.addDirective(directive); - const userConfig = configApi.getUserDefinedConfig(); - expect(userConfig.theme).toBe('dark'); - expect(userConfig.layout).toBeUndefined(); - expect(userConfig.fontFamily).toBeUndefined(); - expect(userConfig.fontSize).toBe(14); + expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(` + { + "fontSize": 14, + "layout": undefined, + "theme": "dark", + } + `); }); - it('should work correctly after reset', () => { + it('should retain config from initialize after reset', () => { const initConfig: MermaidConfig = { theme: 'dark' }; const directive: MermaidConfig = { layout: 'elk' }; configApi.saveConfigFromInitialize(initConfig); configApi.addDirective(directive); - let userConfig = configApi.getUserDefinedConfig(); - expect(userConfig).toEqual({ theme: 'dark', layout: 'elk' }); + expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(` + { + "layout": "elk", + "theme": "dark", + } + `); configApi.reset(); - - userConfig = configApi.getUserDefinedConfig(); - expect(userConfig).toEqual({ theme: 'dark' }); }); }); From e32dc8513f03fe496e2f74a8e907e0ed667d968d Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Tue, 26 Aug 2025 17:36:43 +0900 Subject: [PATCH 37/46] build: stop using `cypress/browsers` for Applitools I'm running into the following error and I'm hoping this will fix it: > Your configFile threw an error from: cypress.config.js > > We stopped running your tests because your config file crashed. > > Error: connect ECONNREFUSED 127.0.0.1:21077 > at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1611:16) on-behalf-of: @Mermaid-Chart --- .github/workflows/e2e-applitools.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/e2e-applitools.yml b/.github/workflows/e2e-applitools.yml index dd97b49e1..6aaa91eb8 100644 --- a/.github/workflows/e2e-applitools.yml +++ b/.github/workflows/e2e-applitools.yml @@ -23,9 +23,6 @@ env: jobs: e2e-applitools: runs-on: ubuntu-latest - container: - image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1 - options: --user 1001 steps: - if: ${{ ! env.USE_APPLI }} name: Warn if not using Applitools From 862d40cc3ad1da222d3470da5663369b9bcc6368 Mon Sep 17 00:00:00 2001 From: matt-baker-agd-systems <69796305+matt-baker-agd-systems@users.noreply.github.com> Date: Tue, 26 Aug 2025 12:04:26 +0100 Subject: [PATCH 38/46] Update Title FAQ based on latest info --- packages/mermaid/src/docs/config/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/docs/config/faq.md b/packages/mermaid/src/docs/config/faq.md index 6d1261fc1..4acf0c3d3 100644 --- a/packages/mermaid/src/docs/config/faq.md +++ b/packages/mermaid/src/docs/config/faq.md @@ -1,6 +1,6 @@ # Frequently Asked Questions -1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/556#issuecomment-363182217) +1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/1433#issuecomment-1991554712) 1. [How to specify custom CSS file?](https://github.com/mermaidjs/mermaid.cli/pull/24#issuecomment-373402785) 1. [How to fix tooltip misplacement issue?](https://github.com/mermaid-js/mermaid/issues/542#issuecomment-3343564621) 1. [How to specify gantt diagram xAxis format?](https://github.com/mermaid-js/mermaid/issues/269#issuecomment-373229136) From 0ef3130510689cbb248cb0e8a651a6c969a85af3 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:13:39 +0000 Subject: [PATCH 39/46] [autofix.ci] apply automated fixes --- docs/config/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config/faq.md b/docs/config/faq.md index db775e438..6d27b658e 100644 --- a/docs/config/faq.md +++ b/docs/config/faq.md @@ -6,7 +6,7 @@ # Frequently Asked Questions -1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/556#issuecomment-363182217) +1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/1433#issuecomment-1991554712) 2. [How to specify custom CSS file?](https://github.com/mermaidjs/mermaid.cli/pull/24#issuecomment-373402785) 3. [How to fix tooltip misplacement issue?](https://github.com/mermaid-js/mermaid/issues/542#issuecomment-3343564621) 4. [How to specify gantt diagram xAxis format?](https://github.com/mermaid-js/mermaid/issues/269#issuecomment-373229136) From b7e9d02b7cc4b297d21a5d0810af1f39781b8e51 Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Tue, 26 Aug 2025 18:10:39 +0530 Subject: [PATCH 40/46] fix: ensure configs from initialize and frontmatter are both handled correctly on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/config.spec.ts | 12 ++++++------ packages/mermaid/src/config.ts | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/mermaid/src/config.spec.ts b/packages/mermaid/src/config.spec.ts index 7fbae03af..7d43ef0f5 100644 --- a/packages/mermaid/src/config.spec.ts +++ b/packages/mermaid/src/config.spec.ts @@ -82,6 +82,7 @@ describe('when working with site config', () => { describe('getUserDefinedConfig', () => { beforeEach(() => { configApi.reset(); + configApi.saveConfigFromInitialize({}); }); it('should return empty object when no user config is defined', () => { @@ -106,7 +107,6 @@ describe('getUserDefinedConfig', () => { expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(` { - "fontFamily": "Arial", "fontSize": 14, "layout": "elk", "theme": "forest", @@ -128,8 +128,8 @@ describe('getUserDefinedConfig', () => { { "fontFamily": "Arial", "fontSize": 14, - "layout": "elk", - "theme": "forest", + "layout": "dagre", + "theme": "dark", } `); }); @@ -152,7 +152,7 @@ describe('getUserDefinedConfig', () => { { "flowchart": { "curve": "basis", - "nodeSpacing": 75, + "nodeSpacing": 50, "rankSpacing": 100, }, "mindmap": { @@ -196,8 +196,8 @@ describe('getUserDefinedConfig', () => { expect(userConfig).toMatchInlineSnapshot(` { "flowchart": { - "curve": "basis", - "nodeSpacing": 100, + "curve": "linear", + "nodeSpacing": 50, "rankSpacing": 100, }, "fontSize": 12, diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts index 4fcb3224d..ce46b993b 100644 --- a/packages/mermaid/src/config.ts +++ b/packages/mermaid/src/config.ts @@ -252,13 +252,13 @@ const checkConfig = (config: MermaidConfig) => { export const getUserDefinedConfig = (): MermaidConfig => { let userConfig: MermaidConfig = {}; - if (configFromInitialize) { - userConfig = assignWithDepth(userConfig, configFromInitialize); - } - for (const d of directives) { userConfig = assignWithDepth(userConfig, d); } + if (configFromInitialize) { + userConfig = assignWithDepth(userConfig, configFromInitialize); + } + return userConfig; }; From f46a151075bd476d7876d115aa9772f93a6b4e60 Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Tue, 26 Aug 2025 19:50:49 +0530 Subject: [PATCH 41/46] Revert "fix: ensure configs from initialize and frontmatter are both handled correctly" This reverts commit b7e9d02b7cc4b297d21a5d0810af1f39781b8e51. --- packages/mermaid/src/config.spec.ts | 12 ++++++------ packages/mermaid/src/config.ts | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/mermaid/src/config.spec.ts b/packages/mermaid/src/config.spec.ts index 7d43ef0f5..7fbae03af 100644 --- a/packages/mermaid/src/config.spec.ts +++ b/packages/mermaid/src/config.spec.ts @@ -82,7 +82,6 @@ describe('when working with site config', () => { describe('getUserDefinedConfig', () => { beforeEach(() => { configApi.reset(); - configApi.saveConfigFromInitialize({}); }); it('should return empty object when no user config is defined', () => { @@ -107,6 +106,7 @@ describe('getUserDefinedConfig', () => { expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(` { + "fontFamily": "Arial", "fontSize": 14, "layout": "elk", "theme": "forest", @@ -128,8 +128,8 @@ describe('getUserDefinedConfig', () => { { "fontFamily": "Arial", "fontSize": 14, - "layout": "dagre", - "theme": "dark", + "layout": "elk", + "theme": "forest", } `); }); @@ -152,7 +152,7 @@ describe('getUserDefinedConfig', () => { { "flowchart": { "curve": "basis", - "nodeSpacing": 50, + "nodeSpacing": 75, "rankSpacing": 100, }, "mindmap": { @@ -196,8 +196,8 @@ describe('getUserDefinedConfig', () => { expect(userConfig).toMatchInlineSnapshot(` { "flowchart": { - "curve": "linear", - "nodeSpacing": 50, + "curve": "basis", + "nodeSpacing": 100, "rankSpacing": 100, }, "fontSize": 12, diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts index ce46b993b..4fcb3224d 100644 --- a/packages/mermaid/src/config.ts +++ b/packages/mermaid/src/config.ts @@ -252,13 +252,13 @@ const checkConfig = (config: MermaidConfig) => { export const getUserDefinedConfig = (): MermaidConfig => { let userConfig: MermaidConfig = {}; - for (const d of directives) { - userConfig = assignWithDepth(userConfig, d); - } - if (configFromInitialize) { userConfig = assignWithDepth(userConfig, configFromInitialize); } + for (const d of directives) { + userConfig = assignWithDepth(userConfig, d); + } + return userConfig; }; From 310fcd22927303e7b371b45485a50507512c8854 Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Thu, 28 Aug 2025 18:24:40 +0530 Subject: [PATCH 42/46] fix: added test cases in mermaidAPI.spec to ensure YAML config takes precedence ovver initialize on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/mermaidAPI.spec.ts | 228 +++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index b2d2d3cd3..60a8306b4 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -1,4 +1,4 @@ -import { assert, beforeEach, describe, expect, it, vi } from 'vitest'; +import { assert, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import assignWithDepth from './assignWithDepth.js'; import type { MermaidConfig } from './config.type.js'; @@ -51,6 +51,27 @@ import { JSDOM } from 'jsdom'; // ------------------------------------------------------------------------------------- +declare global { + interface SVGElement { + getBBox?: () => { + x: number; + y: number; + width: number; + height: number; + top: number; + left: number; + right: number; + bottom: number; + }; + } +} + +beforeAll(() => { + SVGElement.prototype.getBBox ??= function () { + return { x: 0, y: 0, width: 100, height: 20, top: 0, left: 0, right: 100, bottom: 20 }; + }; +}); + describe('mermaidAPI', () => { describe('encodeEntities', () => { it('removes the ending ; from style [text1]:[optional word]#[text2]; with ', () => { @@ -913,4 +934,209 @@ graph TD;A--x|text including URL space|B;`) expect(sequenceDiagram1.db.getActors()).not.toEqual(sequenceDiagram2.db.getActors()); }); }); + + describe('mermaidAPI config precedence', () => { + const id = 'mermaid-config-test'; + + beforeEach(() => { + mermaidAPI.globalReset(); + }); + it('renders with YAML config taking precedence over initialize config', async () => { + mermaid.initialize({ + theme: 'forest', + fontFamily: 'Arial', + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + flowchart: { htmlLabels: false }, + }); + + const diagramText = `--- +config: + theme: base + fontFamily: Courier + themeVariables: + fontFamily: "Courier New" + fontSize: "20px" + flowchart: + htmlLabels: true +--- +flowchart TD + A --> B +`; + + const { svg } = await mermaidAPI.render('yaml-over-init', diagramText); + + const config = mermaidAPI.getConfig(); + expect(config.theme).toBe('base'); + expect(config.fontFamily).toBe('Courier'); + expect(config.themeVariables.fontFamily).toBe('Courier New'); + expect(config.themeVariables.fontSize).toBe('20px'); + expect(config.flowchart?.htmlLabels).toBe(true); + }); + + it('renders with YAML themeVariables fully overriding initialize themeVariables', async () => { + mermaid.initialize({ + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + }); + + const diagramText = `--- +config: + themeVariables: + fontFamily: "Courier New" + fontSize: "20px" +--- +flowchart TD + A --> B +`; + + const { svg } = await mermaidAPI.render(id, diagramText); + const config = mermaidAPI.getConfig(); + + expect(config.themeVariables.fontFamily).toBe('Courier New'); + expect(config.themeVariables.fontSize).toBe('20px'); + expect(config.themeVariables.fontFamily).not.toBe('Arial'); + expect(config.themeVariables.fontSize).not.toBe('16px'); + expect(svg).toContain(' { + mermaid.initialize({ + theme: 'forest', + fontFamily: 'Arial', + themeVariables: { fontFamily: 'Arial', fontSize: '16px', colorPrimary: '#ff0000' }, + }); + + const diagramText = `--- +config: + themeVariables: + fontFamily: "Courier New" +--- +flowchart TD + A --> B +`; + + const { svg } = await mermaidAPI.render(id, diagramText); + + const config = mermaidAPI.getConfig(); + expect(config.themeVariables.fontFamily).toBe('Courier New'); + expect(config.themeVariables.fontSize).toBe('16px'); + expect(config.themeVariables.colorPrimary).toBe('#ff0000'); + }); + + it('renders with YAML config (no themeVariables) and falls back to initialize themeVariables', async () => { + mermaid.initialize({ + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + }); + + const diagramText = `--- +config: + theme: base +--- +flowchart TD + A --> B +`; + + const { svg } = await mermaidAPI.render(id, diagramText); + + const config = mermaidAPI.getConfig(); + expect(config.themeVariables.fontFamily).toBe('Arial'); + expect(config.themeVariables.fontSize).toBe('16px'); + expect(config.theme).toBe('base'); + }); + + it('renders with full YAML config block taking full precedence over initialize config', async () => { + mermaid.initialize({ + theme: 'forest', + fontFamily: 'Arial', + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + flowchart: { htmlLabels: false }, + }); + + const diagramText = `--- +config: + theme: base + fontFamily: Courier + themeVariables: + fontFamily: "Courier New" + fontSize: "20px" + flowchart: + htmlLabels: true +--- +flowchart TD + A --> B +`; + + const { svg } = await mermaidAPI.render('yaml-over-init', diagramText); + + const config = mermaidAPI.getConfig(); + expect(config.theme).toBe('base'); + expect(config.fontFamily).toBe('Courier'); + expect(config.themeVariables.fontFamily).toBe('Courier New'); + expect(config.themeVariables.fontSize).toBe('20px'); + expect(config.flowchart?.htmlLabels).toBe(true); + expect(svg).toContain(' { + mermaid.initialize({ + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + }); + + const diagramText = `--- +config: + theme: base +--- +flowchart TD + A --> B +`; + + await mermaidAPI.render(id, diagramText); + + const config = mermaidAPI.getConfig(); + expect(config.themeVariables.fontFamily).toBe('Arial'); + expect(config.themeVariables.fontSize).toBe('16px'); + expect(config.theme).toBe('base'); + }); + + it('renders with no YAML config so initialize config is fully applied', async () => { + mermaid.initialize({ + theme: 'forest', + fontFamily: 'Arial', + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + }); + + const diagramText = ` +flowchart TD + A --> B +`; + + await mermaidAPI.render(id, diagramText); + + const config = mermaidAPI.getConfig(); + expect(config.theme).toBe('forest'); + expect(config.fontFamily).toBe('Arial'); + expect(config.themeVariables.fontFamily).toBe('Arial'); + expect(config.themeVariables.fontSize).toBe('16px'); + }); + + it('renders with empty YAML config block and falls back to initialize config', async () => { + mermaid.initialize({ + theme: 'dark', + themeVariables: { fontFamily: 'Times', fontSize: '14px' }, + }); + + const diagramText = `--- +config: {} +--- +flowchart TD + A --> B +`; + + await mermaidAPI.render(id, diagramText); + + const config = mermaidAPI.getConfig(); + expect(config.theme).toBe('dark'); + expect(config.themeVariables.fontFamily).toBe('Times'); + expect(config.themeVariables.fontSize).toBe('14px'); + }); + }); }); From d47ba7c2d134c02f7dbbe72adf005c07c82dae05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Fri, 29 Aug 2025 04:50:31 +0800 Subject: [PATCH 43/46] fix: update dependency versions to use caret notation for better compatibility --- packages/mermaid/package.json | 2 +- packages/mermaid/src/docs/package.json | 2 +- pnpm-lock.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 0f455cb1d..9e18739ed 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -68,7 +68,7 @@ }, "dependencies": { "@braintree/sanitize-url": "^7.0.4", - "@iconify/utils": "3.0.1", + "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "workspace:^", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", diff --git a/packages/mermaid/src/docs/package.json b/packages/mermaid/src/docs/package.json index cb54f2ca7..b5b95e7c0 100644 --- a/packages/mermaid/src/docs/package.json +++ b/packages/mermaid/src/docs/package.json @@ -31,7 +31,7 @@ "fast-glob": "^3.3.3", "https-localhost": "^4.7.1", "pathe": "^2.0.3", - "unocss": "66.4.2", + "unocss": "^66.4.2", "unplugin-vue-components": "^28.4.0", "vite": "^6.1.1", "vite-plugin-pwa": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c9772325..b1db33181 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,7 +227,7 @@ importers: specifier: ^7.0.4 version: 7.1.0 '@iconify/utils': - specifier: 3.0.1 + specifier: ^3.0.1 version: 3.0.1 '@mermaid-js/parser': specifier: workspace:^ @@ -499,7 +499,7 @@ importers: specifier: ^2.0.3 version: 2.0.3 unocss: - specifier: 66.4.2 + specifier: ^66.4.2 version: 66.4.2(postcss@8.5.6)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)) unplugin-vue-components: specifier: ^28.4.0 From 01e47333d5f0c8148b8d99acb9807184b8fa4319 Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Fri, 29 Aug 2025 11:31:12 +0530 Subject: [PATCH 44/46] test: mock SVGElement.getBBox using jsdomit for DOM tests on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/mermaidAPI.spec.ts | 215 ++++++++++++------------ 1 file changed, 112 insertions(+), 103 deletions(-) diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index 60a8306b4..ff794abb1 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -1,4 +1,4 @@ -import { assert, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { assert, beforeEach, describe, expect, it, vi } from 'vitest'; import assignWithDepth from './assignWithDepth.js'; import type { MermaidConfig } from './config.type.js'; @@ -41,7 +41,6 @@ import { decodeEntities, encodeEntities } from './utils.js'; import { toBase64 } from './utils/base64.js'; import { StateDB } from './diagrams/state/stateDb.js'; import { ensureNodeFromSelector, jsdomIt } from './tests/util.js'; -import { select } from 'd3'; import { JSDOM } from 'jsdom'; /** @@ -50,28 +49,6 @@ import { JSDOM } from 'jsdom'; */ // ------------------------------------------------------------------------------------- - -declare global { - interface SVGElement { - getBBox?: () => { - x: number; - y: number; - width: number; - height: number; - top: number; - left: number; - right: number; - bottom: number; - }; - } -} - -beforeAll(() => { - SVGElement.prototype.getBBox ??= function () { - return { x: 0, y: 0, width: 100, height: 20, top: 0, left: 0, right: 100, bottom: 20 }; - }; -}); - describe('mermaidAPI', () => { describe('encodeEntities', () => { it('removes the ending ; from style [text1]:[optional word]#[text2]; with ', () => { @@ -941,7 +918,8 @@ graph TD;A--x|text including URL space|B;`) beforeEach(() => { mermaidAPI.globalReset(); }); - it('renders with YAML config taking precedence over initialize config', async () => { + + jsdomIt('renders with YAML config taking precedence over initialize config', async () => { mermaid.initialize({ theme: 'forest', fontFamily: 'Arial', @@ -971,14 +949,19 @@ flowchart TD expect(config.themeVariables.fontFamily).toBe('Courier New'); expect(config.themeVariables.fontSize).toBe('20px'); expect(config.flowchart?.htmlLabels).toBe(true); + + const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document); + expect(svgNode).not.toBeNull(); }); - it('renders with YAML themeVariables fully overriding initialize themeVariables', async () => { - mermaid.initialize({ - themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, - }); + jsdomIt( + 'renders with YAML themeVariables fully overriding initialize themeVariables', + async () => { + mermaid.initialize({ + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + }); - const diagramText = `--- + const diagramText = `--- config: themeVariables: fontFamily: "Courier New" @@ -988,24 +971,29 @@ flowchart TD A --> B `; - const { svg } = await mermaidAPI.render(id, diagramText); - const config = mermaidAPI.getConfig(); + const { svg } = await mermaidAPI.render(id, diagramText); + const config = mermaidAPI.getConfig(); - expect(config.themeVariables.fontFamily).toBe('Courier New'); - expect(config.themeVariables.fontSize).toBe('20px'); - expect(config.themeVariables.fontFamily).not.toBe('Arial'); - expect(config.themeVariables.fontSize).not.toBe('16px'); - expect(svg).toContain(' { - mermaid.initialize({ - theme: 'forest', - fontFamily: 'Arial', - themeVariables: { fontFamily: 'Arial', fontSize: '16px', colorPrimary: '#ff0000' }, - }); + const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document); + expect(svgNode).not.toBeNull(); + } + ); - const diagramText = `--- + jsdomIt( + 'renders with YAML themeVariables overriding only provided keys and keeping others from initialize', + async () => { + mermaid.initialize({ + theme: 'forest', + fontFamily: 'Arial', + themeVariables: { fontFamily: 'Arial', fontSize: '16px', colorPrimary: '#ff0000' }, + }); + + const diagramText = `--- config: themeVariables: fontFamily: "Courier New" @@ -1014,20 +1002,26 @@ flowchart TD A --> B `; - const { svg } = await mermaidAPI.render(id, diagramText); + const { svg } = await mermaidAPI.render(id, diagramText); - const config = mermaidAPI.getConfig(); - expect(config.themeVariables.fontFamily).toBe('Courier New'); - expect(config.themeVariables.fontSize).toBe('16px'); - expect(config.themeVariables.colorPrimary).toBe('#ff0000'); - }); + const config = mermaidAPI.getConfig(); + expect(config.themeVariables.fontFamily).toBe('Courier New'); + expect(config.themeVariables.fontSize).toBe('16px'); + expect(config.themeVariables.colorPrimary).toBe('#ff0000'); - it('renders with YAML config (no themeVariables) and falls back to initialize themeVariables', async () => { - mermaid.initialize({ - themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, - }); + const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document); + expect(svgNode).not.toBeNull(); + } + ); - const diagramText = `--- + jsdomIt( + 'renders with YAML config (no themeVariables) and falls back to initialize themeVariables', + async () => { + mermaid.initialize({ + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + }); + + const diagramText = `--- config: theme: base --- @@ -1035,23 +1029,29 @@ flowchart TD A --> B `; - const { svg } = await mermaidAPI.render(id, diagramText); + const { svg } = await mermaidAPI.render(id, diagramText); - const config = mermaidAPI.getConfig(); - expect(config.themeVariables.fontFamily).toBe('Arial'); - expect(config.themeVariables.fontSize).toBe('16px'); - expect(config.theme).toBe('base'); - }); + const config = mermaidAPI.getConfig(); + expect(config.themeVariables.fontFamily).toBe('Arial'); + expect(config.themeVariables.fontSize).toBe('16px'); + expect(config.theme).toBe('base'); - it('renders with full YAML config block taking full precedence over initialize config', async () => { - mermaid.initialize({ - theme: 'forest', - fontFamily: 'Arial', - themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, - flowchart: { htmlLabels: false }, - }); + const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document); + expect(svgNode).not.toBeNull(); + } + ); - const diagramText = `--- + jsdomIt( + 'renders with full YAML config block taking full precedence over initialize config', + async () => { + mermaid.initialize({ + theme: 'forest', + fontFamily: 'Arial', + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + flowchart: { htmlLabels: false }, + }); + + const diagramText = `--- config: theme: base fontFamily: Courier @@ -1065,23 +1065,28 @@ flowchart TD A --> B `; - const { svg } = await mermaidAPI.render('yaml-over-init', diagramText); + const { svg } = await mermaidAPI.render('yaml-over-init', diagramText); - const config = mermaidAPI.getConfig(); - expect(config.theme).toBe('base'); - expect(config.fontFamily).toBe('Courier'); - expect(config.themeVariables.fontFamily).toBe('Courier New'); - expect(config.themeVariables.fontSize).toBe('20px'); - expect(config.flowchart?.htmlLabels).toBe(true); - expect(svg).toContain(' { - mermaid.initialize({ - themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, - }); + const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document); + expect(svgNode).not.toBeNull(); + } + ); - const diagramText = `--- + jsdomIt( + 'renders with YAML config (no themeVariables) and falls back to initialize themeVariables (duplicate scenario)', + async () => { + mermaid.initialize({ + themeVariables: { fontFamily: 'Arial', fontSize: '16px' }, + }); + + const diagramText = `--- config: theme: base --- @@ -1089,15 +1094,16 @@ flowchart TD A --> B `; - await mermaidAPI.render(id, diagramText); + await mermaidAPI.render(id, diagramText); - const config = mermaidAPI.getConfig(); - expect(config.themeVariables.fontFamily).toBe('Arial'); - expect(config.themeVariables.fontSize).toBe('16px'); - expect(config.theme).toBe('base'); - }); + const config = mermaidAPI.getConfig(); + expect(config.themeVariables.fontFamily).toBe('Arial'); + expect(config.themeVariables.fontSize).toBe('16px'); + expect(config.theme).toBe('base'); + } + ); - it('renders with no YAML config so initialize config is fully applied', async () => { + jsdomIt('renders with no YAML config so initialize config is fully applied', async () => { mermaid.initialize({ theme: 'forest', fontFamily: 'Arial', @@ -1118,25 +1124,28 @@ flowchart TD expect(config.themeVariables.fontSize).toBe('16px'); }); - it('renders with empty YAML config block and falls back to initialize config', async () => { - mermaid.initialize({ - theme: 'dark', - themeVariables: { fontFamily: 'Times', fontSize: '14px' }, - }); + jsdomIt( + 'renders with empty YAML config block and falls back to initialize config', + async () => { + mermaid.initialize({ + theme: 'dark', + themeVariables: { fontFamily: 'Times', fontSize: '14px' }, + }); - const diagramText = `--- + const diagramText = `--- config: {} --- flowchart TD A --> B `; - await mermaidAPI.render(id, diagramText); + await mermaidAPI.render(id, diagramText); - const config = mermaidAPI.getConfig(); - expect(config.theme).toBe('dark'); - expect(config.themeVariables.fontFamily).toBe('Times'); - expect(config.themeVariables.fontSize).toBe('14px'); - }); + const config = mermaidAPI.getConfig(); + expect(config.theme).toBe('dark'); + expect(config.themeVariables.fontFamily).toBe('Times'); + expect(config.themeVariables.fontSize).toBe('14px'); + } + ); }); }); From c6f25167a2c7f771c65341e3ab8294febdb66b3a Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Fri, 29 Aug 2025 19:21:05 +0530 Subject: [PATCH 45/46] fix: handle newline characters as whitespace on-behalf-of: @Mermaid-Chart --- cypress/integration/rendering/classDiagram.spec.js | 13 +++++++++++++ .../mermaid/src/diagrams/class/classDiagram.spec.ts | 8 ++++++++ .../src/diagrams/class/parser/classDiagram.jison | 10 +++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index bd2a96b34..6cea402f8 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -524,5 +524,18 @@ describe('Class diagram', () => { `, {} ); + it('should handle an empty class body with empty braces', () => { + imgSnapshotTest( + ` classDiagram + class FooBase~T~ {} + class Bar { + +Zip + +Zap() + } + FooBase <|-- Ba + `, + { flowchart: { defaultRenderer: 'elk' } } + ); + }); }); }); diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 7c88f2e41..aa5e514e0 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -1070,6 +1070,14 @@ describe('given a class diagram with members and methods ', function () { parser.parse(str); }); + it('should handle an empty class body with {}', function () { + const str = 'classDiagram\nclass EmptyClass {}'; + parser.parse(str); + const actual = parser.yy.getClass('EmptyClass'); + expect(actual.label).toBe('EmptyClass'); + expect(actual.members.length).toBe(0); + expect(actual.methods.length).toBe(0); + }); }); }); diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 0f971c8b9..9a1f991a7 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -293,6 +293,7 @@ classStatement : classIdentifier | classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);} | classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);} + | classIdentifier STRUCT_START STRUCT_STOP {} | classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);} ; @@ -301,8 +302,15 @@ classIdentifier | CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);} ; + +emptyBody + : + | SPACE emptyBody + | NEWLINE emptyBody + ; + annotationStatement - :ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); } + : ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); } ; members From 33bc4a0b4e2ca6d937bb0a8c4e2081b1362b2800 Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Fri, 29 Aug 2025 19:34:29 +0530 Subject: [PATCH 46/46] chore: added changeset on-behalf-of: @Mermaid-Chart --- .changeset/clean-wolves-turn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clean-wolves-turn.md diff --git a/.changeset/clean-wolves-turn.md b/.changeset/clean-wolves-turn.md new file mode 100644 index 000000000..7a44c1c16 --- /dev/null +++ b/.changeset/clean-wolves-turn.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Render newlines as spaces in class diagrams