Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
48ad4c5
chore: separate out shared rendering changes for markdown
darshanr0107 Dec 29, 2025
58ddcde
chore: add changeset
darshanr0107 Dec 29, 2025
ba0139c
refactor: update createLabel and erBox for improved type safety and c…
darshanr0107 Jan 5, 2026
e0c2192
fix(dagre-wrapper): align createLabel with rendering-elements impleme…
darshanr0107 Jan 6, 2026
3afe80d
refactor(createLabel): simplify row handling and class assignment logic
darshanr0107 Jan 9, 2026
71dd6e1
refactor(labelImageUtils): move description to the appropriate function
darshanr0107 Jan 9, 2026
c3ac1cc
Merge branch into shared-rendering-changes-for-markdown
darshanr0107 Jan 12, 2026
445e0a1
refactor(createLabel): clean up import statements
darshanr0107 Jan 12, 2026
490185e
fix: lint issues
darshanr0107 Jan 12, 2026
b11b630
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 12, 2026
47d068b
chore:fix formatting issues
darshanr0107 Jan 12, 2026
79d15e0
Merge branch 'shared-rendering-changes-for-markdown' of https://githu…
darshanr0107 Jan 12, 2026
4eece2a
fix: correct config path for HTML labels in labelHelper function
darshanr0107 Jan 15, 2026
a2debc2
refactor: change createLabel to append node
darshanr0107 Jan 16, 2026
e22d1bf
refactor: add TypeScript types to `createLabel`
aloisklink Jan 15, 2026
7d9824a
refactor: allow passing width: ∞ to createText
aloisklink Jan 15, 2026
7eed6a1
fix: change `createLabel` to call `createText`
aloisklink Jan 15, 2026
a72f289
chore: fix lint issues
darshanr0107 Jan 16, 2026
c97afda
fix: revert the changes in erBox
darshanr0107 Jan 16, 2026
4f12cb9
refactor: update class selection for title and row in createText; tri…
darshanr0107 Jan 19, 2026
4261d3e
Merge pull request #7275 from mermaid-js/shared-rendering-changes-for…
aloisklink Jan 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/olive-mails-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'mermaid': patch
---

fix: change `createLabel` to call `createText`

This adds support for KaTeX and FontAwesome icons loaded via iconpacks in some
older labels. There are some small changes in formatting due to standardizing this code.
2 changes: 1 addition & 1 deletion docs/config/setup/config/functions/evaluate.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Converts a string/boolean into a boolean

String or boolean to convert

`string` | `boolean`
`string` | `boolean` | `null`

## Returns

Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const defaultConfig: MermaidConfig = Object.freeze(config);
* @param val - String or boolean to convert
* @returns The result from the input
*/
export const evaluate = (val?: string | boolean): boolean =>
export const evaluate = (val?: string | boolean | null): boolean =>
val === false || ['false', 'null', '0'].includes(String(val).trim().toLowerCase()) ? false : true;

let siteConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
Expand Down
15 changes: 4 additions & 11 deletions packages/mermaid/src/dagre-wrapper/clusters.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,11 @@ const rect = async (parent, node) => {
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'cluster-label');

// const text = label
// .node()
// .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
// TODO: the createText function returns a `Promise`, so I'm guessing it never runs?
const text =
node.labelType === 'markdown'
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels }, siteConfig)
: label
.node()
.appendChild(await createLabel(node.labelText, node.labelStyle, undefined, true));
: await createLabel(label, node.labelText, node.labelStyle, undefined, true);

// Get the size of the label
let bbox = text.getBBox();
Expand Down Expand Up @@ -144,9 +140,7 @@ const roundedWithTitle = async (parent, node) => {
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
const innerRect = shapeSvg.append('rect');

const text = label
.node()
.appendChild(await createLabel(node.labelText, node.labelStyle, undefined, true));
const text = await createLabel(label, node.labelText, node.labelStyle, undefined, true);

// Get the size of the label
let bbox = text.getBBox();
Expand Down Expand Up @@ -244,8 +238,7 @@ export const insertCluster = async (elem, node) => {
clusterElems[node.id] = await shapes[shape](elem, node);
};
export const getClusterTitleWidth = async (elem, node) => {
const label = await createLabel(node.labelText, node.labelStyle, undefined, true);
elem.node().appendChild(label);
const label = await createLabel(elem, node.labelText, node.labelStyle, undefined, true);
const width = label.getBBox().width;
elem.node().removeChild(label);
return width;
Expand Down
123 changes: 41 additions & 82 deletions packages/mermaid/src/dagre-wrapper/createLabel.js
Original file line number Diff line number Diff line change
@@ -1,96 +1,55 @@
import { select } from 'd3';
import { getConfig } from '../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../config.js';
import { sanitizeText } from '../diagrams/common/common.js';
import { log } from '../logger.js';
import { replaceIconSubstring } from '../rendering-util/createText.js';
import { decodeEntities } from '../utils.js';

/**
* @param dom
* @param styleFn
*/
function applyStyle(dom, styleFn) {
if (styleFn) {
dom.attr('style', styleFn);
}
}

/**
* @param {any} node
* @returns {SVGForeignObjectElement} Node
*/
function addHtmlLabel(node, config) {
const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'));
const div = fo.append('xhtml:div');

const label = node.label;
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
const span = div.append('span');
span.html(sanitizeText(label, config));
applyStyle(span, node.labelStyle);
span.attr('class', labelClass);
import { getConfig } from '../diagram-api/diagramAPI.js';
import { createText } from '../rendering-util/createText.js';

applyStyle(div, node.labelStyle);
div.style('display', 'inline-block');
// Fix for firefox
div.style('white-space', 'nowrap');
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
return fo.node();
}
/**
* @param _vertexText
* @param style
* @param isTitle
* @param isNode
* @param {import('../types.js').D3Selection<SVGGElement>} element - The parent element to which the label will be appended.
* @param {string | [string] | undefined} _vertexText - The text content of the label.
* @param {string} style
* @param {boolean} [isTitle] - If `true`, style this as a title label, else as a normal label.
* @param {boolean} [isNode] - If `true`, style this as a node label, else as an edge label.
* @deprecated svg-util/createText instead
*
* @example
*
* If `getEffectiveHtmlLabels(getConfig())` is `true`, you must reset the width
* and height of the created label after creation, like this:
*
* ```js
* const labelElement = await createLabel(parent, ... );
* let slBox = labelElement.getBBox();
* if (useHtmlLabels) {
* const div = labelElement.children[0];
* const dv = select(labelElement);
* slBox = div.getBoundingClientRect();
* dv.attr('width', slBox.width);
* dv.attr('height', slBox.height);
* }
* parent.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
* ```
*/
const createLabel = async (_vertexText, style, isTitle, isNode) => {
const createLabel = async (element, _vertexText, style, isTitle = false, isNode = false) => {
let vertexText = _vertexText || '';
if (typeof vertexText === 'object') {
vertexText = vertexText[0];
}

const config = getConfig();
if (getEffectiveHtmlLabels(config)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
log.debug('vertexText' + vertexText);
const label = await replaceIconSubstring(decodeEntities(vertexText));
const node = {
isNode,
label,
labelStyle: style.replace('fill:', 'color:'),
};
let vertexNode = addHtmlLabel(node, config);
// vertexNode.parentNode.removeChild(vertexNode);
return vertexNode;
} else {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', style.replace('color:', 'fill:'));
let rows = [];
if (typeof vertexText === 'string') {
rows = vertexText.split(/\\n|\n|<br\s*\/?>/gi);
} else if (Array.isArray(vertexText)) {
rows = vertexText;
} else {
rows = [];
}
const useHtmlLabels = getEffectiveHtmlLabels(config);

for (const row of rows) {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '0');
if (isTitle) {
tspan.setAttribute('class', 'title-row');
} else {
tspan.setAttribute('class', 'row');
}
tspan.textContent = row.trim();
svgLabel.appendChild(tspan);
}
return svgLabel;
}
return await createText(
element,
vertexText,
{
style,
isTitle,
useHtmlLabels,
markdown: false,
isNode,
width: Number.POSITIVE_INFINITY,
},
config
);
};

export default createLabel;
62 changes: 47 additions & 15 deletions packages/mermaid/src/dagre-wrapper/edges.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export const insertEdgeLabel = async (elem, edge) => {
// Create the actual text element
const labelElement =
edge.labelType === 'markdown'
? createText(
? // TODO: the createText function returns a `Promise`, so do we need an wait here?
createText(
elem,
edge.label,
{
Expand All @@ -34,7 +35,7 @@ export const insertEdgeLabel = async (elem, edge) => {
},
config
)
: await createLabel(edge.label, edge.labelStyle);
: await createLabel(elem, edge.label, edge.labelStyle);

// Create outer g, edgeLabel, this will be positioned after graph layout
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
Expand Down Expand Up @@ -64,11 +65,18 @@ export const insertEdgeLabel = async (elem, edge) => {
let fo;
if (edge.startLabelLeft) {
// Create the actual text element
const startLabelElement = await createLabel(edge.startLabelLeft, edge.labelStyle);
const startEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner');
fo = inner.node().appendChild(startLabelElement);
const slBox = startLabelElement.getBBox();
const startLabelElement = await createLabel(inner, edge.startLabelLeft, edge.labelStyle);
fo = startLabelElement;
let slBox = startLabelElement.getBBox();
if (useHtmlLabels) {
const div = startLabelElement.children[0];
const dv = select(startLabelElement);
slBox = div.getBoundingClientRect();
dv.attr('width', slBox.width);
dv.attr('height', slBox.height);
}
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
if (!terminalLabels[edge.id]) {
terminalLabels[edge.id] = {};
Expand All @@ -78,12 +86,23 @@ export const insertEdgeLabel = async (elem, edge) => {
}
if (edge.startLabelRight) {
// Create the actual text element
const startLabelElement = await createLabel(edge.startLabelRight, edge.labelStyle);
const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
fo = startEdgeLabelRight.node().appendChild(startLabelElement);
const startLabelElement = await createLabel(
startEdgeLabelRight,
edge.startLabelRight,
edge.labelStyle
);
fo = startLabelElement;
inner.node().appendChild(startLabelElement);
const slBox = startLabelElement.getBBox();
let slBox = startLabelElement.getBBox();
if (useHtmlLabels) {
const div = startLabelElement.children[0];
const dv = select(startLabelElement);
slBox = div.getBoundingClientRect();
dv.attr('width', slBox.width);
dv.attr('height', slBox.height);
}
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');

if (!terminalLabels[edge.id]) {
Expand All @@ -94,11 +113,18 @@ export const insertEdgeLabel = async (elem, edge) => {
}
if (edge.endLabelLeft) {
// Create the actual text element
const endLabelElement = await createLabel(edge.endLabelLeft, edge.labelStyle);
const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
fo = inner.node().appendChild(endLabelElement);
const slBox = endLabelElement.getBBox();
const endLabelElement = await createLabel(inner, edge.endLabelLeft, edge.labelStyle);
fo = endLabelElement;
let slBox = endLabelElement.getBBox();
if (useHtmlLabels) {
const div = endLabelElement.children[0];
const dv = select(endLabelElement);
slBox = div.getBoundingClientRect();
dv.attr('width', slBox.width);
dv.attr('height', slBox.height);
}
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');

endEdgeLabelLeft.node().appendChild(endLabelElement);
Expand All @@ -111,12 +137,18 @@ export const insertEdgeLabel = async (elem, edge) => {
}
if (edge.endLabelRight) {
// Create the actual text element
const endLabelElement = await createLabel(edge.endLabelRight, edge.labelStyle);
const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');

fo = inner.node().appendChild(endLabelElement);
const slBox = endLabelElement.getBBox();
const endLabelElement = await createLabel(inner, edge.endLabelRight, edge.labelStyle);
fo = endLabelElement;
let slBox = endLabelElement.getBBox();
if (useHtmlLabels) {
const div = endLabelElement.children[0];
const dv = select(endLabelElement);
slBox = div.getBoundingClientRect();
dv.attr('width', slBox.width);
dv.attr('height', slBox.height);
}
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');

endEdgeLabelRight.node().appendChild(endLabelElement);
Expand Down
Loading
Loading