fix: proper mermaid diagram size (#10487)

Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
Alexander Onnikov
2026-02-05 09:21:52 +07:00
committed by GitHub
parent 773fee05be
commit 70895d7338
3 changed files with 60 additions and 23 deletions
+3 -1
View File
@@ -584,9 +584,11 @@ pre.proseCodeBlock>pre.proseCode {
.mermaidPreview {
width: 100%;
display: flex;
justify-content: center;
svg {
width: 100%;
width: auto;
max-width: 100%;
height: auto;
display: block;
@@ -15,7 +15,7 @@
//
-->
<script lang="ts">
import { Html, Modal, ButtonIcon, IconClose, IconMaximize, IconMinimize, Scroller } from '@hcengineering/ui'
import { Modal, ButtonIcon, IconClose, IconMaximize, IconMinimize } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let svg: string
@@ -24,7 +24,7 @@
const dispatch = createEventDispatcher()
</script>
<Modal type={'type-component'} padding={'0.5rem'} bottomPadding={'0'} on:fullsize on:close>
<Modal type={'type-component'} scrollableContent={false} on:fullsize on:close>
<svelte:fragment slot="beforeTitle">
<ButtonIcon
icon={IconClose}
@@ -49,25 +49,46 @@
<div class="hulyHeader-divider short no-print" />
</svelte:fragment>
<Scroller horizontal stickedScrollBars thinScrollBars>
<div class="mermaid-container">
<Html value={svg} />
<div class="mermaid-scroll">
<div class="mermaid-center">
<div class="mermaid-container">
<!-- Mermaid renders SVG with securityLevel=antiscript; render as-is to preserve SVG text -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html svg}
</div>
</div>
</Scroller>
</div>
</Modal>
<style>
.mermaid-container {
.mermaid-scroll {
width: 100%;
height: 100%;
padding: 0.5rem;
overflow: auto;
box-sizing: border-box;
scrollbar-width: auto;
}
.mermaid-center {
min-width: 100%;
min-height: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
width: 100%;
align-items: center;
}
.mermaid-container {
width: max-content;
height: max-content;
}
.mermaid-container :global(svg) {
max-width: 100%;
width: auto;
height: auto;
max-width: none;
max-height: none;
display: block;
margin: 0 auto;
margin: 0;
}
</style>
@@ -473,24 +473,38 @@ function buildState (
// Ensure SVG maintains its natural size
const svg = container.querySelector('svg')
if (svg !== null) {
svg.style.width = '100%'
svg.style.width = 'auto'
svg.style.maxWidth = '100%'
svg.style.height = 'auto'
svg.style.display = 'block'
// Remove any width/height attributes that might cause stretching
const widthAttr = svg.getAttribute('width')
const heightAttr = svg.getAttribute('height')
const viewBox = svg.getAttribute('viewBox')
const viewBoxParts = viewBox
?.trim()
.split(/[\s,]+/)
.map((v) => Number.parseFloat(v))
const viewBoxWidth = viewBoxParts?.length === 4 ? viewBoxParts[2] : NaN
const viewBoxHeight = viewBoxParts?.length === 4 ? viewBoxParts[3] : NaN
const widthIsPercent = widthAttr?.trim().endsWith('%') ?? false
const heightIsPercent = heightAttr?.trim().endsWith('%') ?? false
if ((widthAttr !== undefined || widthIsPercent) && Number.isFinite(viewBoxWidth)) {
svg.setAttribute('width', String(viewBoxWidth))
}
if ((heightAttr !== undefined || heightIsPercent) && Number.isFinite(viewBoxHeight)) {
svg.setAttribute('height', String(viewBoxHeight))
}
// If no viewBox, derive one from numeric width/height to allow scaling down
if (!svg.hasAttribute('viewBox')) {
const width = svg.getAttribute('width')
const height = svg.getAttribute('height')
const widthValue = width !== null ? Number.parseFloat(width) : NaN
const heightValue = height !== null ? Number.parseFloat(height) : NaN
const widthValue = widthAttr !== null ? Number.parseFloat(widthAttr) : NaN
const heightValue = heightAttr !== null ? Number.parseFloat(heightAttr) : NaN
if (Number.isFinite(widthValue) && Number.isFinite(heightValue)) {
svg.setAttribute('viewBox', `0 0 ${widthValue} ${heightValue}`)
svg.removeAttribute('width')
svg.removeAttribute('height')
}
} else {
svg.removeAttribute('width')
svg.removeAttribute('height')
}
}