diff --git a/.changeset/tough-baths-hunt.md b/.changeset/tough-baths-hunt.md
new file mode 100644
index 00000000000..283b548df7e
--- /dev/null
+++ b/.changeset/tough-baths-hunt.md
@@ -0,0 +1,8 @@
+---
+'mermaid': patch
+---
+
+fix: prevent escaping `<` and `&` when `htmlLabels: false`
+
+user: @aloisklink
+user: @BambioGaming
diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js
index bcee3d899e3..801a1256a0d 100644
--- a/cypress/integration/rendering/flowchart-v2.spec.js
+++ b/cypress/integration/rendering/flowchart-v2.spec.js
@@ -71,6 +71,15 @@ describe('Flowchart v2', () => {
{ htmlLabels: false, flowchart: { htmlLabels: false } }
);
});
+ it('5a: angle brackets should be work without html labels', () => {
+ imgSnapshotTest(
+ `flowchart TD
+ a["**Plain text**:\n 5 > 3 && 2 < 4"]
+ b["\`**Markdown**:
5 > 3 && 2 < 4\`"]
+ `,
+ { htmlLabels: false }
+ );
+ });
it('6: should render non-escaped with html labels', () => {
imgSnapshotTest(
`flowchart TD
diff --git a/packages/mermaid/src/diagrams/class/shapeUtil.ts b/packages/mermaid/src/diagrams/class/shapeUtil.ts
index 94c8f817aef..967b91b2b66 100644
--- a/packages/mermaid/src/diagrams/class/shapeUtil.ts
+++ b/packages/mermaid/src/diagrams/class/shapeUtil.ts
@@ -146,7 +146,7 @@ async function addText(
numberOfLines = text.children.length;
const textChild = text.children[0];
- if (text.textContent === '' || text.textContent.includes('>')) {
+ if (text.textContent === '' || text.textContent!.includes('>')) {
textChild.textContent =
textContent[0] +
textContent.substring(1).replaceAll('>', '>').replaceAll('<', '<').trim();
diff --git a/packages/mermaid/src/rendering-util/createText.spec.ts b/packages/mermaid/src/rendering-util/createText.spec.ts
index dd7bc00b644..0a46222682d 100644
--- a/packages/mermaid/src/rendering-util/createText.spec.ts
+++ b/packages/mermaid/src/rendering-util/createText.spec.ts
@@ -1,7 +1,8 @@
+import { select } from 'd3';
import { describe, expect, it } from 'vitest';
import { sanitizeText } from '../diagram-api/diagramAPI.js';
import mermaid from '../mermaid.js';
-import { replaceIconSubstring } from './createText.js';
+import { createText, replaceIconSubstring } from './createText.js';
describe('replaceIconSubstring', () => {
it('converts FontAwesome icon notations to HTML tags', async () => {
@@ -61,3 +62,41 @@ describe('replaceIconSubstring', () => {
expect(output).toContain(expected);
});
});
+
+describe('createText', () => {
+ beforeEach(() => {
+ // JSDom has no SVGTSpanElement, so we need to mock getComputedTextLength to avoid errors in createText
+ const mock = vi.mockObject(window.SVGElement.prototype);
+ (mock as unknown as SVGTSpanElement).getComputedTextLength = vi.fn(() => 123.456);
+ });
+
+ it.for([
+ {
+ useHtmlLabels: false,
+ markdown: true,
+ },
+ {
+ useHtmlLabels: true,
+ markdown: true,
+ },
+ {
+ useHtmlLabels: false,
+ markdown: false,
+ },
+ {
+ useHtmlLabels: true,
+ markdown: false,
+ },
+ ])(
+ 'decodes HTML entities in text when useHtmlLabels is $useHtmlLabels and markdown is $markdown',
+ async ({ useHtmlLabels, markdown }) => {
+ const input = '5 > 3 && 2 < 4';
+ const expected = '5 > 3 && 2 < 4';
+
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ const svgGroup = svg.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
+ const output = await createText(select(svgGroup), input, { useHtmlLabels, markdown });
+ expect(output.textContent).toEqual(expected);
+ }
+ );
+});
diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts
index 165fa77bd97..bdb777cab73 100644
--- a/packages/mermaid/src/rendering-util/createText.ts
+++ b/packages/mermaid/src/rendering-util/createText.ts
@@ -1,5 +1,3 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-// @ts-nocheck TODO: Fix types
import { select } from 'd3';
import type { MermaidConfig } from '../config.type.js';
import type { SVGGroup } from '../diagram-api/types.js';
@@ -17,8 +15,12 @@ import { getIconSVG, isIconAvailable } from './icons.js';
import { splitLineToFitWidth } from './splitText.js';
import type { MarkdownLine, MarkdownWord } from './types.js';
import { getConfig } from '../config.js';
+import type { D3Selection } from '../types.js';
-function applyStyle(dom, styleFn) {
+function applyStyle(
+ dom: d3.Selection,
+ styleFn?: Parameters[1]
+) {
if (styleFn) {
dom.attr('style', styleFn);
}
@@ -28,10 +30,10 @@ function applyStyle(dom, styleFn) {
const maxSafeSizeForWidth = 16384;
async function addHtmlSpan(
- element,
- node,
- width,
- classes,
+ element: D3Selection,
+ node: { label: string; labelStyle: string; isNode: boolean },
+ width: number,
+ classes: string,
addBackground = false,
// TODO: Make config mandatory
config: MermaidConfig = getConfig()
@@ -42,7 +44,7 @@ async function addHtmlSpan(
fo.attr('width', `${Math.min(10 * width, maxSafeSizeForWidth)}px`);
fo.attr('height', `${Math.min(10 * width, maxSafeSizeForWidth)}px`);
- const div = fo.append('xhtml:div');
+ const div = fo.append('xhtml:div');
const sanitizedLabel = hasKatex(node.label)
? await renderKatexSanitized(node.label.replace(common.lineBreakRegex, '\n'), config)
: sanitizeText(node.label, config);
@@ -65,15 +67,15 @@ async function addHtmlSpan(
div.attr('class', 'labelBkg');
}
- let bbox = div.node().getBoundingClientRect();
+ let bbox = div.node()!.getBoundingClientRect();
if (bbox.width === width) {
div.style('display', 'table');
div.style('white-space', 'break-spaces');
div.style('width', width + 'px');
- bbox = div.node().getBoundingClientRect();
+ bbox = div.node()!.getBoundingClientRect();
}
- return fo.node();
+ return fo.node()!;
}
/**
@@ -84,7 +86,11 @@ async function addHtmlSpan(
* @param lineHeight - The line height value for the text.
* @returns The created tspan element.
*/
-function createTspan(textElement: any, lineIndex: number, lineHeight: number) {
+function createTspan(
+ textElement: D3Selection,
+ lineIndex: number,
+ lineHeight: number
+) {
return textElement
.append('tspan')
.attr('class', 'text-outer-tspan')
@@ -93,11 +99,15 @@ function createTspan(textElement: any, lineIndex: number, lineHeight: number) {
.attr('dy', lineHeight + 'em');
}
-function computeWidthOfText(parentNode: any, lineHeight: number, line: MarkdownLine): number {
+function computeWidthOfText(
+ parentNode: D3Selection,
+ lineHeight: number,
+ line: MarkdownLine
+): number {
const testElement = parentNode.append('text');
const testSpan = createTspan(testElement, 1, lineHeight);
updateTextContentAndStyles(testSpan, line);
- const textLength = testSpan.node().getComputedTextLength();
+ const textLength = testSpan.node()!.getComputedTextLength();
testElement.remove();
return textLength;
}
@@ -128,7 +138,7 @@ export function computeDimensionOfText(
*/
function createFormattedText(
width: number,
- g: any,
+ g: D3Selection,
structuredText: MarkdownWord[][],
addBackground = false
) {
@@ -153,7 +163,7 @@ function createFormattedText(
}
}
if (addBackground) {
- const bbox = textElement.node().getBBox();
+ const bbox = textElement.node()!.getBBox();
const padding = 2;
bkg
.attr('x', bbox.x - padding)
@@ -161,12 +171,38 @@ function createFormattedText(
.attr('width', bbox.width + 2 * padding)
.attr('height', bbox.height + 2 * padding);
- return labelGroup.node();
+ return labelGroup.node()!;
} else {
- return textElement.node();
+ return textElement.node()!;
}
}
+/**
+ * Our HTML code uses `.innerHTML` to apply the text,
+ * however our plain text SVG code uses `.textContent` to apply the text,
+ * which means that HTML entities are not decoded in SVG text.
+ *
+ * This means that we need to decode any HTML entities that `sanitizeText` encodes.
+ *
+ * TODO: If we're using `.textContent`, we can probably skip sanitization entirely.
+ */
+function decodeHTMLEntities(text: string): string {
+ // We only need to decode the few entries that `sanitizeText` encodes.
+ const regex = /&(amp|lt|gt);/g;
+ return text.replace(regex, (match, entity) => {
+ switch (entity) {
+ case 'amp':
+ return '&';
+ case 'lt':
+ return '<';
+ case 'gt':
+ return '>';
+ default:
+ return match;
+ }
+ });
+}
+
/**
* Updates the text content and styles of the given tspan element based on the
* provided wrappedLine data.
@@ -174,7 +210,10 @@ function createFormattedText(
* @param tspan - The tspan element to update.
* @param wrappedLine - The line data to apply to the tspan element.
*/
-function updateTextContentAndStyles(tspan: any, wrappedLine: MarkdownWord[]) {
+function updateTextContentAndStyles(
+ tspan: D3Selection,
+ wrappedLine: MarkdownWord[]
+) {
tspan.text('');
wrappedLine.forEach((word, index) => {
@@ -184,10 +223,10 @@ function updateTextContentAndStyles(tspan: any, wrappedLine: MarkdownWord[]) {
.attr('class', 'text-inner-tspan')
.attr('font-weight', word.type === 'strong' ? 'bold' : 'normal');
if (index === 0) {
- innerTspan.text(word.content);
+ innerTspan.text(decodeHTMLEntities(word.content));
} else {
// TODO: check what joiner to use.
- innerTspan.text(' ' + word.content);
+ innerTspan.text(' ' + decodeHTMLEntities(word.content));
}
});
}
@@ -226,8 +265,22 @@ export async function replaceIconSubstring(
// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to set classes to 'nodeLabel' when isNode=true otherwise 'edgeLabel'
// When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row'
+/**
+ * Creates a text element within the given SVG group element.
+ *
+ * If `markdown` is `true`, basic markdown syntax will be processed.
+ * Otherwise, if:
+ * - `useHtmlLabels` is `true`, the text will be sanitized and set in `` as HTML.
+ * - `useHtmlLabels` is `false`, the text will be added as a `` element using `.text`
+ *
+ * @param el - The parent SVG `` element to append the text element to.
+ * @param text - The text content to be displayed.
+ * @param options - Optional options
+ * @param config - Mermaid configuration object
+ * @returns The created text element, either a `` or a `` element depending on the options.
+ */
export const createText = async (
- el,
+ el: D3Selection,
text = '',
{
style = '',
@@ -273,7 +326,7 @@ export const createText = async (
return vertexNode;
} else {
//sometimes the user might add br tags with 1 or more spaces in between, so we need to replace them with
- const sanitizeBR = text.replace(/
/g, '
');
+ const sanitizeBR = decodeEntities(text.replace(/
/g, '
'));
const structuredText = markdown
? markdownToLines(sanitizeBR.replace('
', '
'), config)
: nonMarkdownToLines(sanitizeBR);
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/bowTieRect.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/bowTieRect.ts
index d4b41103f8b..9429c020dca 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/bowTieRect.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/bowTieRect.ts
@@ -75,8 +75,8 @@ export async function bowTieRect(parent: D3Selecti
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
- const w = bbox.width + node.padding + 20;
- const h = bbox.height + node.padding;
+ const w = bbox.width + (node.padding ?? 0) + 20;
+ const h = bbox.height + (node.padding ?? 0);
const ry = h / 2;
const rx = ry / (2.5 + h / 50);
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/card.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/card.ts
index 4aaf9222a7a..0f004944dfa 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/card.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/card.ts
@@ -19,9 +19,9 @@ export async function card(parent: D3Selection,
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
- const h = bbox.height + node.padding;
+ const h = bbox.height + (node.padding ?? 0);
const padding = 12;
- const w = bbox.width + node.padding + padding;
+ const w = bbox.width + (node.padding ?? 0) + padding;
const left = 0;
const right = w;
const top = -h;
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/cylinder.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/cylinder.ts
index 9d2cd63f6d7..f09653bc32e 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/cylinder.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/cylinder.ts
@@ -54,10 +54,10 @@ export async function cylinder(parent: D3Selection
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
- const w = Math.max(bbox.width + node.padding, node.width ?? 0);
+ const w = Math.max(bbox.width + (node.padding ?? 0), node.width ?? 0);
const rx = w / 2;
const ry = rx / (2.5 + w / 50);
- const h = Math.max(bbox.height + ry + node.padding, node.height ?? 0);
+ const h = Math.max(bbox.height + ry + (node.padding ?? 0), node.height ?? 0);
let cylinder: D3Selection | D3Selection;
const { cssStyles } = node;
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/dividedRect.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/dividedRect.ts
index 77df5815541..a25ccd39b57 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/dividedRect.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/dividedRect.ts
@@ -12,8 +12,8 @@ export async function dividedRectangle(
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
- const w = bbox.width + node.padding;
- const h = bbox.height + node.padding;
+ const w = bbox.width + (node.padding ?? 0);
+ const h = bbox.height + (node.padding ?? 0);
const rectOffset = h * 0.2;
const x = -w / 2;
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/document.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/document.ts
index d5ea3aa7620..5d59b463f71 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/document.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/document.ts
@@ -54,10 +54,10 @@ export async function cylinder(parent: D3Selection
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
- const w = bbox.width + node.padding;
+ const w = bbox.width + (node.padding ?? 0);
const rx = w / 2;
const ry = rx / (2.5 + w / 50);
- const h = bbox.height + ry + node.padding;
+ const h = bbox.height + ry + (node.padding ?? 0);
let cylinder: D3Selection | D3Selection;
const { cssStyles } = node;
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/erBox.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/erBox.ts
index 9dd4c90e2b2..adecf2e0c00 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/erBox.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/erBox.ts
@@ -350,18 +350,18 @@ async function addText(
);
// Undo work around now that text passed through correctly
if (labelText.includes('<') || labelText.includes('>')) {
- let child = text.children[0];
- child.textContent = child.textContent.replaceAll('<', '<').replaceAll('>', '>');
+ let child: Element | ChildNode = text.children[0];
+ child.textContent = child.textContent!.replaceAll('<', '<').replaceAll('>', '>');
while (child.childNodes[0]) {
child = child.childNodes[0];
// Replace its text content
- child.textContent = child.textContent.replaceAll('<', '<').replaceAll('>', '>');
+ child.textContent = child.textContent!.replaceAll('<', '<').replaceAll('>', '>');
}
}
let bbox = text.getBBox();
if (evaluate(config.htmlLabels)) {
- const div = text.children[0];
+ const div = text.children[0] as HTMLDivElement;
div.style.textAlign = 'start';
const dv = select(text);
bbox = div.getBoundingClientRect();
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/hexagon.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/hexagon.ts
index 52a4397a2f6..910cb7b6c01 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/hexagon.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/hexagon.ts
@@ -30,9 +30,9 @@ export async function hexagon(parent: D3Selection<
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const f = 4;
- const h = bbox.height + node.padding;
+ const h = bbox.height + (node.padding ?? 0);
const m = h / f;
- const w = bbox.width + 2 * m + node.padding;
+ const w = bbox.width + 2 * m + (node.padding ?? 0);
const points = [
{ x: m, y: 0 },
{ x: w - m, y: 0 },
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/question.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/question.ts
index 87adc4814b7..988d0794782 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/question.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/question.ts
@@ -22,8 +22,8 @@ export async function question(parent: D3Selection
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
- const w = bbox.width + node.padding;
- const h = bbox.height + node.padding;
+ const w = bbox.width + (node.padding ?? 0);
+ const h = bbox.height + (node.padding ?? 0);
const s = w + h;
const adjustment = 0.5;
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/requirementBox.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/requirementBox.ts
index ed764f5eecc..ed6c43b9ba8 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/requirementBox.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/requirementBox.ts
@@ -198,12 +198,11 @@ async function addText(
if (!useHtmlLabels) {
const textChild = text.children[0];
for (const child of textChild.children) {
- child.textContent = child.textContent.replaceAll('>', '>').replaceAll('<', '<');
if (style) {
child.setAttribute('style', style);
}
}
- // Get the bounding box after the text update
+ // Get the bounding box after the style update
bbox = text.getBBox();
// Add extra height so it is similar to the html labels
bbox.height += 6;
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/stadium.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stadium.ts
index 117176b197d..32b00191180 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/stadium.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stadium.ts
@@ -61,8 +61,8 @@ export async function stadium(parent: D3Selection<
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
- const h = bbox.height + node.padding;
- const w = bbox.width + h / 4 + node.padding;
+ const h = bbox.height + (node.padding ?? 0);
+ const w = bbox.width + h / 4 + (node.padding ?? 0);
const radius = h / 2;
const { cssStyles } = node;
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/subroutine.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/subroutine.ts
index ab24af29c87..746e684b837 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/subroutine.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/subroutine.ts
@@ -37,9 +37,9 @@ export async function subroutine(parent: D3Selecti
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
- const halfPadding = (node?.padding || 0) / 2;
- const w = bbox.width + node.padding;
- const h = bbox.height + node.padding;
+ const halfPadding = (node.padding ?? 0) / 2;
+ const w = bbox.width + (node.padding ?? 0);
+ const h = bbox.height + (node.padding ?? 0);
const x = -bbox.width / 2 - halfPadding;
const y = -bbox.height / 2 - halfPadding;
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/text.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/text.ts
index cf253fd1750..417b7b9409d 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/text.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/text.ts
@@ -10,8 +10,8 @@ export async function text(parent: D3Selection,
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
- const totalWidth = Math.max(bbox.width + node.padding, node?.width || 0);
- const totalHeight = Math.max(bbox.height + node.padding, node?.height || 0);
+ const totalWidth = Math.max(bbox.width + (node.padding ?? 0), node?.width || 0);
+ const totalHeight = Math.max(bbox.height + (node.padding ?? 0), node?.height || 0);
const x = -totalWidth / 2;
const y = -totalHeight / 2;
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/trapezoid.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/trapezoid.ts
index d0228d55bd8..1a0efee9a27 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/trapezoid.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/trapezoid.ts
@@ -26,8 +26,8 @@ export async function trapezoid(parent: D3Selectio
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
- const w = bbox.width + node.padding;
- const h = bbox.height + node.padding;
+ const w = bbox.width + (node.padding ?? 0);
+ const h = bbox.height + (node.padding ?? 0);
const points = [
{ x: (-3 * h) / 6, y: 0 },
{ x: w + (3 * h) / 6, y: 0 },
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts
index 568a08c39cc..d06df9d97b1 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts
@@ -63,7 +63,7 @@ export const labelHelper = async (
const halfPadding = (node?.padding ?? 0) / 2;
if (useHtmlLabels) {
- const div = text.children[0];
+ const div = text.children[0] as HTMLDivElement;
const dv = select(text);
// if there are images, need to wait for them to load before getting the bounding box
diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts
index 0f1bfbecfd1..4a61a993716 100644
--- a/packages/mermaid/src/utils.ts
+++ b/packages/mermaid/src/utils.ts
@@ -918,6 +918,7 @@ export const encodeEntities = function (text: string): string {
};
/**
+ * Partially reverts encoding done via {@link encodeEntities}
*
* @param text - text to be decoded
* @returns