fix(mermaid): themechange detector + expand simplification
This commit is contained in:
		@@ -12,7 +12,8 @@ class DiagramPanZoom {
 | 
			
		||||
  private scale = 1
 | 
			
		||||
  private readonly MIN_SCALE = 0.5
 | 
			
		||||
  private readonly MAX_SCALE = 3
 | 
			
		||||
  private readonly ZOOM_SENSITIVITY = 0.001
 | 
			
		||||
 | 
			
		||||
  cleanups: (() => void)[] = []
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private container: HTMLElement,
 | 
			
		||||
@@ -20,19 +21,33 @@ class DiagramPanZoom {
 | 
			
		||||
  ) {
 | 
			
		||||
    this.setupEventListeners()
 | 
			
		||||
    this.setupNavigationControls()
 | 
			
		||||
    this.resetTransform()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private setupEventListeners() {
 | 
			
		||||
    // Mouse drag events
 | 
			
		||||
    this.container.addEventListener("mousedown", this.onMouseDown.bind(this))
 | 
			
		||||
    document.addEventListener("mousemove", this.onMouseMove.bind(this))
 | 
			
		||||
    document.addEventListener("mouseup", this.onMouseUp.bind(this))
 | 
			
		||||
    const mouseDownHandler = this.onMouseDown.bind(this)
 | 
			
		||||
    const mouseMoveHandler = this.onMouseMove.bind(this)
 | 
			
		||||
    const mouseUpHandler = this.onMouseUp.bind(this)
 | 
			
		||||
    const resizeHandler = this.resetTransform.bind(this)
 | 
			
		||||
 | 
			
		||||
    // Wheel zoom events
 | 
			
		||||
    this.container.addEventListener("wheel", this.onWheel.bind(this), { passive: false })
 | 
			
		||||
    this.container.addEventListener("mousedown", mouseDownHandler)
 | 
			
		||||
    document.addEventListener("mousemove", mouseMoveHandler)
 | 
			
		||||
    document.addEventListener("mouseup", mouseUpHandler)
 | 
			
		||||
    window.addEventListener("resize", resizeHandler)
 | 
			
		||||
 | 
			
		||||
    // Reset on window resize
 | 
			
		||||
    window.addEventListener("resize", this.resetTransform.bind(this))
 | 
			
		||||
    this.cleanups.push(
 | 
			
		||||
      () => this.container.removeEventListener("mousedown", mouseDownHandler),
 | 
			
		||||
      () => document.removeEventListener("mousemove", mouseMoveHandler),
 | 
			
		||||
      () => document.removeEventListener("mouseup", mouseUpHandler),
 | 
			
		||||
      () => window.removeEventListener("resize", resizeHandler),
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cleanup() {
 | 
			
		||||
    for (const cleanup of this.cleanups) {
 | 
			
		||||
      cleanup()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private setupNavigationControls() {
 | 
			
		||||
@@ -84,26 +99,6 @@ class DiagramPanZoom {
 | 
			
		||||
    this.container.style.cursor = "grab"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private onWheel(e: WheelEvent) {
 | 
			
		||||
    e.preventDefault()
 | 
			
		||||
 | 
			
		||||
    const delta = -e.deltaY * this.ZOOM_SENSITIVITY
 | 
			
		||||
    const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
 | 
			
		||||
 | 
			
		||||
    // Calculate mouse position relative to content
 | 
			
		||||
    const rect = this.content.getBoundingClientRect()
 | 
			
		||||
    const mouseX = e.clientX - rect.left
 | 
			
		||||
    const mouseY = e.clientY - rect.top
 | 
			
		||||
 | 
			
		||||
    // Adjust pan to zoom around mouse position
 | 
			
		||||
    const scaleDiff = newScale - this.scale
 | 
			
		||||
    this.currentPan.x -= mouseX * scaleDiff
 | 
			
		||||
    this.currentPan.y -= mouseY * scaleDiff
 | 
			
		||||
 | 
			
		||||
    this.scale = newScale
 | 
			
		||||
    this.updateTransform()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private zoom(delta: number) {
 | 
			
		||||
    const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
 | 
			
		||||
 | 
			
		||||
@@ -126,7 +121,11 @@ class DiagramPanZoom {
 | 
			
		||||
 | 
			
		||||
  private resetTransform() {
 | 
			
		||||
    this.scale = 1
 | 
			
		||||
    this.currentPan = { x: 0, y: 0 }
 | 
			
		||||
    const svg = this.content.querySelector("svg")!
 | 
			
		||||
    this.currentPan = {
 | 
			
		||||
      x: svg.getBoundingClientRect().width / 2,
 | 
			
		||||
      y: svg.getBoundingClientRect().height / 2,
 | 
			
		||||
    }
 | 
			
		||||
    this.updateTransform()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -149,38 +148,59 @@ document.addEventListener("nav", async () => {
 | 
			
		||||
  const nodes = center.querySelectorAll("code.mermaid") as NodeListOf<HTMLElement>
 | 
			
		||||
  if (nodes.length === 0) return
 | 
			
		||||
 | 
			
		||||
  const computedStyleMap = cssVars.reduce(
 | 
			
		||||
    (acc, key) => {
 | 
			
		||||
      acc[key] = getComputedStyle(document.documentElement).getPropertyValue(key)
 | 
			
		||||
      return acc
 | 
			
		||||
    },
 | 
			
		||||
    {} as Record<(typeof cssVars)[number], string>,
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  mermaidImport ||= await import(
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    "https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.4.0/mermaid.esm.min.mjs"
 | 
			
		||||
  )
 | 
			
		||||
  const mermaid = mermaidImport.default
 | 
			
		||||
 | 
			
		||||
  const darkMode = document.documentElement.getAttribute("saved-theme") === "dark"
 | 
			
		||||
  mermaid.initialize({
 | 
			
		||||
    startOnLoad: false,
 | 
			
		||||
    securityLevel: "loose",
 | 
			
		||||
    theme: darkMode ? "dark" : "base",
 | 
			
		||||
    themeVariables: {
 | 
			
		||||
      fontFamily: computedStyleMap["--codeFont"],
 | 
			
		||||
      primaryColor: computedStyleMap["--light"],
 | 
			
		||||
      primaryTextColor: computedStyleMap["--darkgray"],
 | 
			
		||||
      primaryBorderColor: computedStyleMap["--tertiary"],
 | 
			
		||||
      lineColor: computedStyleMap["--darkgray"],
 | 
			
		||||
      secondaryColor: computedStyleMap["--secondary"],
 | 
			
		||||
      tertiaryColor: computedStyleMap["--tertiary"],
 | 
			
		||||
      clusterBkg: computedStyleMap["--light"],
 | 
			
		||||
      edgeLabelBackground: computedStyleMap["--highlight"],
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
  await mermaid.run({ nodes })
 | 
			
		||||
  const textMapping: WeakMap<HTMLElement, string> = new WeakMap()
 | 
			
		||||
  for (const node of nodes) {
 | 
			
		||||
    textMapping.set(node, node.innerText)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function renderMermaid() {
 | 
			
		||||
    // de-init any other diagrams
 | 
			
		||||
    for (const node of nodes) {
 | 
			
		||||
      node.removeAttribute("data-processed")
 | 
			
		||||
      const oldText = textMapping.get(node)
 | 
			
		||||
      if (oldText) {
 | 
			
		||||
        node.innerHTML = oldText
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const computedStyleMap = cssVars.reduce(
 | 
			
		||||
      (acc, key) => {
 | 
			
		||||
        acc[key] = window.getComputedStyle(document.documentElement).getPropertyValue(key)
 | 
			
		||||
        return acc
 | 
			
		||||
      },
 | 
			
		||||
      {} as Record<(typeof cssVars)[number], string>,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    const darkMode = document.documentElement.getAttribute("saved-theme") === "dark"
 | 
			
		||||
    mermaid.initialize({
 | 
			
		||||
      startOnLoad: false,
 | 
			
		||||
      securityLevel: "loose",
 | 
			
		||||
      theme: darkMode ? "dark" : "base",
 | 
			
		||||
      themeVariables: {
 | 
			
		||||
        fontFamily: computedStyleMap["--codeFont"],
 | 
			
		||||
        primaryColor: computedStyleMap["--light"],
 | 
			
		||||
        primaryTextColor: computedStyleMap["--darkgray"],
 | 
			
		||||
        primaryBorderColor: computedStyleMap["--tertiary"],
 | 
			
		||||
        lineColor: computedStyleMap["--darkgray"],
 | 
			
		||||
        secondaryColor: computedStyleMap["--secondary"],
 | 
			
		||||
        tertiaryColor: computedStyleMap["--tertiary"],
 | 
			
		||||
        clusterBkg: computedStyleMap["--light"],
 | 
			
		||||
        edgeLabelBackground: computedStyleMap["--highlight"],
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await mermaid.run({ nodes })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  await renderMermaid()
 | 
			
		||||
  document.addEventListener("themechange", renderMermaid)
 | 
			
		||||
  window.addCleanup(() => document.removeEventListener("themechange", renderMermaid))
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < nodes.length; i++) {
 | 
			
		||||
    const codeBlock = nodes[i] as HTMLElement
 | 
			
		||||
@@ -203,7 +223,6 @@ document.addEventListener("nav", async () => {
 | 
			
		||||
    if (!popupContainer) return
 | 
			
		||||
 | 
			
		||||
    let panZoom: DiagramPanZoom | null = null
 | 
			
		||||
 | 
			
		||||
    function showMermaid() {
 | 
			
		||||
      const container = popupContainer.querySelector("#mermaid-space") as HTMLElement
 | 
			
		||||
      const content = popupContainer.querySelector(".mermaid-content") as HTMLElement
 | 
			
		||||
@@ -224,24 +243,15 @@ document.addEventListener("nav", async () => {
 | 
			
		||||
 | 
			
		||||
    function hideMermaid() {
 | 
			
		||||
      popupContainer.classList.remove("active")
 | 
			
		||||
      panZoom?.cleanup()
 | 
			
		||||
      panZoom = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function handleEscape(e: any) {
 | 
			
		||||
      if (e.key === "Escape") {
 | 
			
		||||
        hideMermaid()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const closeBtn = popupContainer.querySelector(".close-button") as HTMLButtonElement
 | 
			
		||||
 | 
			
		||||
    closeBtn.addEventListener("click", hideMermaid)
 | 
			
		||||
    expandBtn.addEventListener("click", showMermaid)
 | 
			
		||||
    registerEscapeHandler(popupContainer, hideMermaid)
 | 
			
		||||
    document.addEventListener("keydown", handleEscape)
 | 
			
		||||
 | 
			
		||||
    window.addCleanup(() => {
 | 
			
		||||
      closeBtn.removeEventListener("click", hideMermaid)
 | 
			
		||||
      panZoom?.cleanup()
 | 
			
		||||
      expandBtn.removeEventListener("click", showMermaid)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -53,46 +53,16 @@ pre {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & > #mermaid-space {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    width: 90%;
 | 
			
		||||
    height: 90vh;
 | 
			
		||||
    margin: 5vh auto;
 | 
			
		||||
    background: var(--light);
 | 
			
		||||
    box-shadow:
 | 
			
		||||
      0 14px 50px rgba(27, 33, 48, 0.12),
 | 
			
		||||
      0 10px 30px rgba(27, 33, 48, 0.16);
 | 
			
		||||
    border: 1px solid var(--lightgray);
 | 
			
		||||
    background-color: var(--light);
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    transform: translate(-50%, -50%);
 | 
			
		||||
    height: 80vh;
 | 
			
		||||
    width: 80vw;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    & > .mermaid-header {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      justify-content: flex-end;
 | 
			
		||||
      padding: 1rem;
 | 
			
		||||
      border-bottom: 1px solid var(--lightgray);
 | 
			
		||||
      background: var(--light);
 | 
			
		||||
      z-index: 2;
 | 
			
		||||
      max-height: fit-content;
 | 
			
		||||
 | 
			
		||||
      & > .close-button {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        width: 32px;
 | 
			
		||||
        height: 32px;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        background: transparent;
 | 
			
		||||
        border: none;
 | 
			
		||||
        border-radius: 4px;
 | 
			
		||||
        color: var(--darkgray);
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        transition: all 0.2s ease;
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background: var(--lightgray);
 | 
			
		||||
          color: var(--dark);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & > .mermaid-content {
 | 
			
		||||
      padding: 2rem;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user