diff --git a/.gitignore b/.gitignore
index c6e3baf7..59488e74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,6 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+
+.claude
+CLAUDE.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8069f151..9d73430f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
## Unreleased
+### v2.1.0
+
+- Upgrade mermaid to 11.12.1
+- Add ERD support
+
### Features
- Support ClassDef for styling the nodes by [@ad1992](https://github.com/ad1992) in https://github.com/excalidraw/mermaid-to-excalidraw/pull/71
diff --git a/package.json b/package.json
index 5578427d..f7f43fb2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@excalidraw/mermaid-to-excalidraw",
- "version": "1.1.4",
+ "version": "2.1.1",
"description": "Mermaid to Excalidraw Diagrams",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -11,7 +11,8 @@
"scripts": {
"build": "rimraf -rf ./dist && cross-env tsc -b src",
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
- "start": "vite playground",
+ "dev": "vite playground --mode development",
+ "start": "vite playground --mode development",
"build:playground": "tsc --noEmit --project ./playground/tsconfig.json && vite build playground",
"preview": "yarn run build:playground && vite preview --outDir ./public",
"test": "vitest",
@@ -20,15 +21,15 @@
},
"dependencies": {
"@excalidraw/markdown-to-text": "0.1.2",
- "mermaid": "10.9.4",
- "nanoid": "4.0.2",
- "react-split": "^2.0.14"
+ "@mermaid-js/parser": "^0.6.3",
+ "mermaid": "^11.12.1",
+ "nanoid": "4.0.2"
},
"devDependencies": {
"@babel/core": "7.12.0",
"@excalidraw/eslint-config": "1.0.3",
- "@excalidraw/excalidraw": "0.17.1-7381-cdf6d3e",
- "@types/mermaid": "9.2.0",
+ "@excalidraw/excalidraw": "^0.18.0-816c81c",
+ "@types/mermaid": "^9.2.0",
"@types/react": "18.2.14",
"@types/react-dom": "18.2.4",
"@typescript-eslint/eslint-plugin": "5.59.9",
diff --git a/playground/CustomTest.tsx b/playground/CustomTest.tsx
index 5b6c061a..1f851007 100644
--- a/playground/CustomTest.tsx
+++ b/playground/CustomTest.tsx
@@ -1,5 +1,7 @@
+import { useState, useEffect } from "react";
import { MermaidDiagram } from "./MermaidDiagram.tsx";
import type { ActiveTestCaseIndex, MermaidData } from "./index.tsx";
+import { usePersistedSectionState } from "./usePersistedSectionState.ts";
interface CustomTestProps {
onChange: (
@@ -10,57 +12,112 @@ interface CustomTestProps {
activeTestCaseIndex: ActiveTestCaseIndex;
}
+const STORAGE_KEY = "mermaid-to-excalidraw-definition";
+
const CustomTest = ({
onChange,
mermaidData,
activeTestCaseIndex,
}: CustomTestProps) => {
const isActive = activeTestCaseIndex === "custom";
+ const {
+ isExpanded: isParsedDataExpanded,
+ handleToggle: handleParsedDataToggle,
+ } = usePersistedSectionState("custom:parsed-data");
+ const [textareaValue, setTextareaValue] = useState(() => {
+ // Load from localStorage on initial mount
+ try {
+ return localStorage.getItem(STORAGE_KEY) || "";
+ } catch {
+ return "";
+ }
+ });
+
+ // Update textarea when mermaidData changes from external source
+ useEffect(() => {
+ if (mermaidData.definition && activeTestCaseIndex !== "custom") {
+ setTextareaValue(mermaidData.definition);
+ }
+ }, [mermaidData.definition, activeTestCaseIndex]);
+
return (
<>
{isActive && (
<>
-
+
+ {"Live Mermaid SVG"}
+
+
+
+
-
+
{"Parsed data from parseMermaid"}
-
- {JSON.stringify(mermaidData.output, null, 2)}
-
- {mermaidData.error && {mermaidData.error}
}
+ {isParsedDataExpanded ? (
+ <>
+
+ {JSON.stringify(mermaidData.output, null, 2)}
+
+ {mermaidData.error && {mermaidData.error}
}
+ >
+ ) : null}
>
)}
diff --git a/playground/ExcalidrawSvgPreview.tsx b/playground/ExcalidrawSvgPreview.tsx
new file mode 100644
index 00000000..daca5758
--- /dev/null
+++ b/playground/ExcalidrawSvgPreview.tsx
@@ -0,0 +1,132 @@
+import { useEffect, useState } from "react";
+import {
+ convertToExcalidrawElements,
+ exportToSvg,
+} from "@excalidraw/excalidraw";
+import { DEFAULT_FONT_SIZE } from "../src/constants";
+import { graphToExcalidraw } from "../src/graphToExcalidraw";
+import { parseMermaid } from "../src/parseMermaid";
+
+interface ExcalidrawSvgPreviewProps {
+ definition: string;
+}
+
+let previewRenderQueue: Promise = Promise.resolve();
+const svgCache = new Map();
+const inFlightSvgCache = new Map>();
+
+const runSequentially = (task: () => Promise) => {
+ const run = previewRenderQueue.then(task, task);
+ previewRenderQueue = run.then(
+ () => undefined,
+ () => undefined
+ );
+ return run;
+};
+
+const cleanupMermaidTempContainers = () => {
+ document
+ .querySelectorAll(".mermaid-to-excalidraw-svg-container")
+ .forEach((container) => {
+ container.remove();
+ });
+};
+
+const generateExcalidrawSvg = async (definition: string): Promise => {
+ const cachedSvg = svgCache.get(definition);
+ if (cachedSvg) {
+ return cachedSvg;
+ }
+
+ const pendingRender = inFlightSvgCache.get(definition);
+ if (pendingRender) {
+ return pendingRender;
+ }
+
+ const renderPromise = runSequentially(async () => {
+ try {
+ const parsedMermaid = await parseMermaid(definition);
+ const { elements, files } = graphToExcalidraw(parsedMermaid, {
+ fontSize: DEFAULT_FONT_SIZE,
+ });
+
+ const svgElement = await exportToSvg({
+ elements: convertToExcalidrawElements(elements),
+ appState: {
+ exportBackground: true,
+ viewBackgroundColor: "#ffffff",
+ },
+ files: files ?? null,
+ exportPadding: 16,
+ });
+
+ return svgElement.outerHTML;
+ } finally {
+ cleanupMermaidTempContainers();
+ }
+ })
+ .then((svg) => {
+ svgCache.set(definition, svg);
+ return svg;
+ })
+ .finally(() => {
+ inFlightSvgCache.delete(definition);
+ });
+
+ inFlightSvgCache.set(definition, renderPromise);
+ return renderPromise;
+};
+
+export const ExcalidrawSvgPreview = ({
+ definition,
+}: ExcalidrawSvgPreviewProps) => {
+ const [svg, setSvg] = useState("");
+ const [error, setError] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+
+ useEffect(() => {
+ let isCancelled = false;
+
+ setIsLoading(true);
+ setError(null);
+
+ generateExcalidrawSvg(definition)
+ .then((renderedSvg) => {
+ if (!isCancelled) {
+ setSvg(renderedSvg);
+ }
+ })
+ .catch((err) => {
+ if (!isCancelled) {
+ setSvg("");
+ setError(String(err));
+ }
+ })
+ .finally(() => {
+ if (!isCancelled) {
+ setIsLoading(false);
+ }
+ });
+
+ return () => {
+ isCancelled = true;
+ };
+ }, [definition]);
+
+ if (error) {
+ return {error}
;
+ }
+
+ if (isLoading && !svg) {
+ return (
+ {"Rendering Excalidraw SVG..."}
+ );
+ }
+
+ return (
+
+ );
+};
diff --git a/playground/ExcalidrawWrapper.tsx b/playground/ExcalidrawWrapper.tsx
index f1b4a452..9f1391c1 100644
--- a/playground/ExcalidrawWrapper.tsx
+++ b/playground/ExcalidrawWrapper.tsx
@@ -3,7 +3,7 @@ import {
Excalidraw,
convertToExcalidrawElements,
} from "@excalidraw/excalidraw";
-import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types/types.js";
+import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
import { graphToExcalidraw } from "../src/graphToExcalidraw";
import { DEFAULT_FONT_SIZE } from "../src/constants";
import type { MermaidData } from "./";
@@ -11,22 +11,26 @@ import type { MermaidData } from "./";
interface ExcalidrawWrapperProps {
mermaidDefinition: MermaidData["definition"];
mermaidOutput: MermaidData["output"];
+ theme: "light" | "dark";
+ apiRef?: React.MutableRefObject;
}
const ExcalidrawWrapper = ({
mermaidDefinition,
mermaidOutput,
+ theme,
+ apiRef,
}: ExcalidrawWrapperProps) => {
- const [excalidrawAPI, setExcalidrawAPI] =
+ const [readyExcalidrawAPI, setReadyExcalidrawAPI] =
useState(null);
useEffect(() => {
- if (!excalidrawAPI) {
+ if (!readyExcalidrawAPI || readyExcalidrawAPI.isDestroyed) {
return;
}
if (mermaidDefinition === "" || mermaidOutput === null) {
- excalidrawAPI.resetScene();
+ readyExcalidrawAPI.resetScene();
return;
}
@@ -34,28 +38,42 @@ const ExcalidrawWrapper = ({
fontSize: DEFAULT_FONT_SIZE,
});
- excalidrawAPI.updateScene({
+ readyExcalidrawAPI.updateScene({
elements: convertToExcalidrawElements(elements),
});
- excalidrawAPI.scrollToContent(excalidrawAPI.getSceneElements(), {
+ readyExcalidrawAPI.scrollToContent(readyExcalidrawAPI.getSceneElements(), {
fitToContent: true,
});
if (files) {
- excalidrawAPI.addFiles(Object.values(files));
+ readyExcalidrawAPI.addFiles(Object.values(files));
}
- }, [mermaidDefinition, mermaidOutput]);
+ }, [mermaidDefinition, mermaidOutput, readyExcalidrawAPI]);
return (
setExcalidrawAPI(api)}
+ onExcalidrawAPI={(api) => {
+ if (apiRef) {
+ apiRef.current = api;
+ }
+ }}
+ onInitialize={(api) => {
+ setReadyExcalidrawAPI(api);
+ }}
+ onUnmount={() => {
+ setReadyExcalidrawAPI(null);
+ if (apiRef) {
+ apiRef.current = null;
+ }
+ }}
/>
);
diff --git a/playground/GithubCorner.scss b/playground/GithubCorner.scss
index b70f4877..13a09c7a 100644
--- a/playground/GithubCorner.scss
+++ b/playground/GithubCorner.scss
@@ -1,10 +1,20 @@
.github-corner {
- height: fit-content;
+ position: absolute;
+ top: 0;
+ right: 0;
+ color: #fff;
+ z-index: 3;
+ border-top-right-radius: 10px;
+ overflow: hidden;
+
+ svg {
+ width: 50px;
+ height: 50px;
+ }
}
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
-
}
@keyframes octocat-wave {
@@ -25,10 +35,20 @@
}
@media (max-width: 500px) {
+ .github-corner {
+ top: -12px;
+ right: -12px;
+
+ svg {
+ width: 64px;
+ height: 64px;
+ }
+ }
+
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
-
+
.github-corner:hover .octo-arm {
animation: none;
}
diff --git a/playground/MermaidDiagram.tsx b/playground/MermaidDiagram.tsx
index f55decc8..0381e76c 100644
--- a/playground/MermaidDiagram.tsx
+++ b/playground/MermaidDiagram.tsx
@@ -1,44 +1,59 @@
-import { useState, useTransition, useEffect } from "react";
+import { useEffect, useState } from "react";
import mermaid from "mermaid";
+import { runMermaidTaskSequentially } from "../src/mermaidExecutionQueue.ts";
interface MermaidProps {
id: string;
definition: string;
}
+let renderCounter = 0;
+
export const MermaidDiagram = ({ definition, id }: MermaidProps) => {
const [svg, setSvg] = useState("");
- const [error, setError] = useState(null);
- const [, startTransition] = useTransition();
useEffect(() => {
- const render = async (id: string, definition: string) => {
- try {
- setError(null);
+ let isCancelled = false;
+ // Mermaid removes any existing DOM node with the render ID before drawing.
+ const renderId = `mermaid-diagram-${id}-${renderCounter++}`;
+ const tempContainer = document.createElement("div");
+ tempContainer.setAttribute(
+ "style",
+ `opacity: 0; position: fixed; z-index: -1; left: -99999px; top: -99999px;`
+ );
+
+ if (!definition.trim()) {
+ setSvg("");
+ return;
+ }
+
+ document.body.appendChild(tempContainer);
- const { svg } = await mermaid.render(
- `mermaid-diagram-${id}`,
- definition
+ const render = async (definition: string) => {
+ try {
+ const { svg } = await runMermaidTaskSequentially(() =>
+ mermaid.render(renderId, definition, tempContainer)
);
- startTransition(() => {
+
+ if (!isCancelled) {
setSvg(svg);
- });
+ }
} catch (err) {
- setError(String(err));
+ if (!isCancelled) {
+ setSvg("");
+ }
+ } finally {
+ tempContainer.remove();
}
};
- render(id, definition);
+ render(definition);
+
+ return () => {
+ isCancelled = true;
+ tempContainer.remove();
+ };
}, [definition, id]);
- return (
- <>
-
- {error && {error}
}
- >
- );
+ return ;
};
diff --git a/playground/SingleTestCase.tsx b/playground/SingleTestCase.tsx
index 9fada20b..72496ae6 100644
--- a/playground/SingleTestCase.tsx
+++ b/playground/SingleTestCase.tsx
@@ -1,38 +1,178 @@
+import { useEffect, useRef, useState } from "react";
import { MermaidDiagram } from "./MermaidDiagram";
+import { ExcalidrawSvgPreview } from "./ExcalidrawSvgPreview";
export interface TestCase {
- type: "class" | "flowchart" | "sequence" | "unsupported";
+ type: "class" | "erd" | "flowchart" | "sequence" | "unsupported";
name: string;
definition: string;
}
export interface SingleTestCaseProps {
testcase: TestCase;
- onChange: (activeTestcaseIndex: number) => void;
+ onChange: (definition: string) => void;
+ onInsertMermaidSvg: (svgHtml: string, width: number, height: number) => void;
index: number;
activeTestcaseIndex?: number;
}
-const SingleTestCase = ({ testcase, onChange, index }: SingleTestCaseProps) => {
+type CopyState = "idle" | "copied" | "error";
+
+const copyToClipboard = async (text: string) => {
+ if (navigator.clipboard?.writeText) {
+ await navigator.clipboard.writeText(text);
+ return;
+ }
+
+ const textarea = document.createElement("textarea");
+ textarea.value = text;
+ textarea.setAttribute("readonly", "");
+ textarea.style.position = "fixed";
+ textarea.style.opacity = "0";
+ document.body.appendChild(textarea);
+ textarea.select();
+
+ const didCopy = document.execCommand("copy");
+ textarea.remove();
+
+ if (!didCopy) {
+ throw new Error("Clipboard copy failed");
+ }
+};
+
+const SingleTestCase = ({
+ testcase,
+ onChange,
+ onInsertMermaidSvg,
+ index,
+}: SingleTestCaseProps) => {
const { name, definition, type } = testcase;
+ const [editableDefinition, setEditableDefinition] = useState(definition);
+ const [copyState, setCopyState] = useState("idle");
+ const resetCopyStateTimeoutRef = useRef(null);
+ const mermaidSvgRef = useRef(null);
+
+ useEffect(() => {
+ setEditableDefinition(definition);
+ }, [definition]);
+
+ useEffect(() => {
+ return () => {
+ if (resetCopyStateTimeoutRef.current !== null) {
+ window.clearTimeout(resetCopyStateTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ const copyButtonLabel =
+ copyState === "copied"
+ ? "Copied"
+ : copyState === "error"
+ ? "Retry"
+ : "Copy";
+
return (
- <>
- {name}
- {definition}
-
-
-
- >
+
+
+
+
+
+
+
+
+
+
+
+ {"Mermaid SVG"}
+ {
+ const svgEl = mermaidSvgRef.current?.querySelector("svg");
+ if (svgEl) {
+ let width: number;
+ let height: number;
+
+ const vb = svgEl.viewBox?.baseVal;
+ if (vb && vb.width > 0 && vb.height > 0) {
+ width = vb.width;
+ height = vb.height;
+ } else {
+ const rect = svgEl.getBoundingClientRect();
+ width = rect.width;
+ height = rect.height;
+ }
+
+ onInsertMermaidSvg(svgEl.outerHTML, width, height);
+ }
+ }}
+ >
+
+
+
+
+
+ {"Excalidraw SVG"}
+ {
+ onChange(editableDefinition);
+ }}
+ >
+
+
+
+
+
);
};
diff --git a/playground/Testcases.tsx b/playground/Testcases.tsx
index 8a4d273d..06571ac0 100644
--- a/playground/Testcases.tsx
+++ b/playground/Testcases.tsx
@@ -1,65 +1,154 @@
-import { Fragment } from "react";
-
import { FLOWCHART_DIAGRAM_TESTCASES } from "./testcases/flowchart";
import { SEQUENCE_DIAGRAM_TESTCASES } from "./testcases/sequence.ts";
import { CLASS_DIAGRAM_TESTCASES } from "./testcases/class.ts";
+import { ERD_DIAGRAM_TESTCASES } from "./testcases/er.ts";
import { UNSUPPORTED_DIAGRAM_TESTCASES } from "./testcases/unsupported.ts";
import SingleTestCase, { TestCase } from "./SingleTestCase.tsx";
import type { ActiveTestCaseIndex, MermaidData } from "./index.tsx";
+import { usePersistedSectionState } from "./usePersistedSectionState.ts";
interface TestcasesProps {
onChange: (
definition: MermaidData["definition"],
activeTestCaseIndex: number | "custom" | null
) => void;
+ onInsertMermaidSvg: (svgHtml: string, width: number, height: number) => void;
activeTestCaseIndex: ActiveTestCaseIndex;
}
-const Testcases = ({ onChange }: TestcasesProps) => {
- const testcaseTypes: { name: string; testcases: TestCase[] }[] = [
- { name: "Flowchart", testcases: FLOWCHART_DIAGRAM_TESTCASES },
- { name: "Sequence", testcases: SEQUENCE_DIAGRAM_TESTCASES },
- { name: "Class", testcases: CLASS_DIAGRAM_TESTCASES },
- { name: "Unsupported", testcases: UNSUPPORTED_DIAGRAM_TESTCASES },
- ];
+interface TestcaseSectionProps {
+ name: string;
+ testcases: TestCase[];
+ startIndex: number;
+ documentationHref: string;
+ onChange: TestcasesProps["onChange"];
+ onInsertMermaidSvg: TestcasesProps["onInsertMermaidSvg"];
+}
+
+const TestcaseSection = ({
+ name,
+ testcases,
+ startIndex,
+ documentationHref,
+ onChange,
+ onInsertMermaidSvg,
+}: TestcaseSectionProps) => {
+ const baseId = name.toLowerCase();
+ const { isExpanded, handleToggle } = usePersistedSectionState(
+ `testcases:${baseId}`,
+ name === "Class"
+ );
+
+ return (
+
+
+
+ {name} {"Diagram Examples"}
+
+
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ window.open(documentationHref, "_blank", "noopener,noreferrer");
+ }}
+ >
+ {"Docs"}
+
+ {testcases.length}
+
+
+ {isExpanded ? (
+
+ {testcases.map((testcase, index) => {
+ const testcaseIndex = startIndex + index;
+
+ return (
+ {
+ onChange(definition, testcaseIndex);
+ }}
+ onInsertMermaidSvg={onInsertMermaidSvg}
+ testcase={testcase}
+ />
+ );
+ })}
+
+ ) : null}
+
+ );
+};
- const allTestCases = testcaseTypes.flatMap((type) => type.testcases);
+const Testcases = ({ onChange, onInsertMermaidSvg }: TestcasesProps) => {
+ const testcaseTypes: {
+ name: string;
+ testcases: TestCase[];
+ documentationHref: string;
+ startIndex: number;
+ }[] = [
+ {
+ name: "Flowchart",
+ testcases: FLOWCHART_DIAGRAM_TESTCASES,
+ documentationHref: "https://mermaid.js.org/syntax/flowchart.html",
+ },
+ {
+ name: "Sequence",
+ testcases: SEQUENCE_DIAGRAM_TESTCASES,
+ documentationHref: "https://mermaid.js.org/syntax/sequenceDiagram.html",
+ },
+ {
+ name: "Class",
+ testcases: CLASS_DIAGRAM_TESTCASES,
+ documentationHref: "https://mermaid.js.org/syntax/classDiagram.html",
+ },
+ {
+ name: "ERD",
+ testcases: ERD_DIAGRAM_TESTCASES,
+ documentationHref:
+ "https://mermaid.js.org/syntax/entityRelationshipDiagram.html",
+ },
+ {
+ name: "Unsupported",
+ testcases: UNSUPPORTED_DIAGRAM_TESTCASES,
+ documentationHref: "https://mermaid.js.org/intro/syntax-reference.html",
+ },
+ ].map((section, index, sections) => ({
+ ...section,
+ startIndex: sections
+ .slice(0, index)
+ .reduce((total, current) => total + current.testcases.length, 0),
+ }));
- let testCaseIndex = 0;
return (
-
- {testcaseTypes.map(({ name, testcases }) => {
- const baseId = name.toLowerCase();
- return (
-
-
- {name} {"Diagrams"}
-
-
-
- {name} {"Diagram Examples"}
-
-
- {testcases.map((testcase, index) => {
- return (
- {
- const { definition } = allTestCases[index];
- onChange(definition, index);
- }}
- testcase={testcase}
- />
- );
- })}
-
-
-
- );
- })}
-
+ <>
+ {testcaseTypes.map(
+ ({ name, testcases, documentationHref, startIndex }) => {
+ return (
+
+ );
+ }
+ )}
+ >
);
};
diff --git a/playground/index.html b/playground/index.html
index bf3505c2..be1cf52e 100644
--- a/playground/index.html
+++ b/playground/index.html
@@ -6,6 +6,11 @@
+
diff --git a/playground/index.tsx b/playground/index.tsx
index bea72011..e600c257 100644
--- a/playground/index.tsx
+++ b/playground/index.tsx
@@ -1,10 +1,15 @@
-import { useState, useCallback, useDeferredValue } from "react";
+import { useState, useCallback, useDeferredValue, useEffect, useRef } from "react";
+import {
+ convertToExcalidrawElements,
+} from "@excalidraw/excalidraw";
+import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
import CustomTest from "./CustomTest.tsx";
import ExcalidrawWrapper from "./ExcalidrawWrapper.tsx";
import Testcases from "./Testcases.tsx";
import { parseMermaid } from "../src/parseMermaid.ts";
import GitHubCorner from "./GitHubCorner.tsx";
-import Split from "react-split";
+
+import "@excalidraw/excalidraw/index.css";
export interface MermaidData {
definition: string;
@@ -14,7 +19,56 @@ export interface MermaidData {
export type ActiveTestCaseIndex = number | "custom" | null;
+const THEME_STORAGE_KEY = "mermaid-to-excalidraw-theme";
+
+type ThemeMode = "light" | "dark";
+
+interface ThemeState {
+ mode: ThemeMode;
+ isUserPreference: boolean;
+}
+
+const getSystemThemeMode = (): ThemeMode => {
+ if (typeof window === "undefined") {
+ return "light";
+ }
+
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
+ ? "dark"
+ : "light";
+};
+
+const readStoredThemeMode = (): ThemeMode | null => {
+ if (typeof window === "undefined") {
+ return null;
+ }
+
+ try {
+ const storedThemeMode = window.sessionStorage.getItem(THEME_STORAGE_KEY);
+ return storedThemeMode === "dark" || storedThemeMode === "light"
+ ? storedThemeMode
+ : null;
+ } catch (error) {
+ console.error("Failed to read theme from sessionStorage:", error);
+ return null;
+ }
+};
+
+const writeStoredThemeMode = (mode: ThemeMode) => {
+ if (typeof window === "undefined") {
+ return;
+ }
+
+ try {
+ window.sessionStorage.setItem(THEME_STORAGE_KEY, mode);
+ } catch (error) {
+ console.error("Failed to save theme to sessionStorage:", error);
+ }
+};
+
const App = () => {
+ const excalidrawAPIRef = useRef(null);
+
const [mermaidData, setMermaidData] = useState({
definition: "",
error: null,
@@ -23,8 +77,119 @@ const App = () => {
const [activeTestCaseIndex, setActiveTestCaseIndex] =
useState(null);
+ const [themeState, setThemeState] = useState(() => {
+ const storedThemeMode = readStoredThemeMode();
+ if (storedThemeMode) {
+ return {
+ mode: storedThemeMode,
+ isUserPreference: true,
+ };
+ }
+
+ return {
+ mode: getSystemThemeMode(),
+ isUserPreference: false,
+ };
+ });
+ const isDarkMode = themeState.mode === "dark";
const deferredMermaidData = useDeferredValue(mermaidData);
+ const toggleTheme = useCallback(() => {
+ setThemeState((currentThemeState) => {
+ const nextThemeMode =
+ currentThemeState.mode === "dark" ? "light" : "dark";
+ writeStoredThemeMode(nextThemeMode);
+
+ return {
+ mode: nextThemeMode,
+ isUserPreference: true,
+ };
+ });
+ }, []);
+
+ useEffect(() => {
+ if (isDarkMode) {
+ document.body.classList.add("dark-mode");
+ } else {
+ document.body.classList.remove("dark-mode");
+ }
+ }, [isDarkMode]);
+
+ // Listen for system theme changes
+ useEffect(() => {
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ const handleChange = (e: MediaQueryListEvent) => {
+ setThemeState((currentThemeState) => {
+ if (currentThemeState.isUserPreference) {
+ return currentThemeState;
+ }
+
+ return {
+ mode: e.matches ? "dark" : "light",
+ isUserPreference: false,
+ };
+ });
+ };
+
+ mediaQuery.addEventListener("change", handleChange);
+ return () => mediaQuery.removeEventListener("change", handleChange);
+ }, []);
+
+ // Add keyboard shortcut for dark mode toggle (Alt+Shift+D)
+ useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.altKey && event.shiftKey && event.key === "D") {
+ event.preventDefault();
+ toggleTheme();
+ }
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+ return () => window.removeEventListener("keydown", handleKeyDown);
+ }, [toggleTheme]);
+
+ const handleInsertMermaidSvg = useCallback(
+ (svgHtml: string, width: number, height: number) => {
+ const api = excalidrawAPIRef.current;
+ if (!api || api.isDestroyed) {
+ return;
+ }
+
+ const dataURL = `data:image/svg+xml;base64,${btoa(
+ unescape(encodeURIComponent(svgHtml))
+ )}`;
+ const fileId = `mermaid-svg-${Date.now()}`;
+
+ api.addFiles([
+ {
+ id: fileId as any,
+ dataURL: dataURL as any,
+ mimeType: "image/svg+xml" as any,
+ created: Date.now(),
+ lastRetrieved: Date.now(),
+ },
+ ]);
+
+ api.updateScene({
+ elements: [
+ ...api.getSceneElements(),
+ ...convertToExcalidrawElements([
+ {
+ type: "image",
+ fileId: fileId as any,
+ width,
+ height,
+ x: 0,
+ y: 0,
+ },
+ ]),
+ ],
+ });
+ api.scrollToContent(api.getSceneElements(), { fitToContent: true });
+ },
+ []
+ );
+
const handleOnChange = useCallback(
async (
definition: MermaidData["definition"],
@@ -40,7 +205,15 @@ const App = () => {
output: mermaid,
error: null,
});
+
+ // Save to localStorage whenever a diagram is successfully rendered
+ try {
+ localStorage.setItem("mermaid-to-excalidraw-definition", definition);
+ } catch (storageError) {
+ console.error("Failed to save to localStorage:", storageError);
+ }
} catch (error) {
+ console.error({ error, definition });
setMermaidData({
definition,
output: null,
@@ -52,61 +225,66 @@ const App = () => {
);
return (
-
-
-
-
+ <>
+
+
+
-
-
+
+
+
+
+
-
+ >
);
};
diff --git a/playground/style.scss b/playground/style.scss
index 03163a31..0a383245 100644
--- a/playground/style.scss
+++ b/playground/style.scss
@@ -1,109 +1,797 @@
-body {
- margin-top: 0;
- margin-bottom: 0;
- padding: 0;
- font-family: sans-serif;
- #flowchart-container,
- #unsupported {
- max-width: 48%;
+:root {
+ --bg: rgba(255, 251, 245, 0.88);
+ --bg-strong: #fbf7f1;
+ --testcase-section: #f5ecdf;
+ --testcase-card-bg: rgba(255, 255, 255, 0.6);
+ // --testcase-card-bg: rgba(255, 255, 255, 0.38)
+ --panel-border: rgba(91, 68, 49, 0.14);
+ --panel-border-strong: rgba(91, 68, 49, 0.2);
+ --text: #231912;
+ --muted: #6d5d4f;
+ --accent: #cd6a3e;
+ --accent-strong: #a64b2d;
+ --accent-soft: rgba(205, 106, 62, 0.14);
+ --secondary: #1d6c72;
+ --secondary-soft: rgba(29, 108, 114, 0.14);
+ --code-bg: #241c19;
+ --code-text: #f6e7d3;
+ --shadow-lg: 0 28px 80px rgba(79, 52, 33, 0.16);
+ --shadow-md: 0 18px 45px rgba(79, 52, 33, 0.1);
+ --shadow-sm: 0 10px 24px rgba(79, 52, 33, 0.08);
+ --radius-xl: 18px;
+ --radius-lg: 14px;
+ --radius-md: 10px;
+}
+
+body.dark-mode {
+ --bg: #211915;
+ --bg-strong: #2d231e;
+ --testcase-section: #312621;
+ // --testcase-card-bg: #29211d;
+ --testcase-card-bg: rgba(255, 255, 255, 0.03);
+ --panel-border: rgba(255, 228, 201, 0.08);
+ --panel-border-strong: rgba(255, 228, 201, 0.16);
+ --text: #f1e4d6;
+ --muted: #bea58d;
+ --accent: #e58b61;
+ --accent-strong: #f2a47d;
+ --accent-soft: rgba(229, 139, 97, 0.18);
+ --secondary: #85cfd2;
+ --secondary-soft: rgba(133, 207, 210, 0.18);
+ --code-bg: #17110f;
+ --code-text: #f7eadc;
+ --shadow-lg: 0 28px 80px rgba(0, 0, 0, 0.42);
+ --shadow-md: 0 18px 45px rgba(0, 0, 0, 0.28);
+ --shadow-sm: 0 10px 24px rgba(0, 0, 0, 0.18);
+
+}
+
+@keyframes rise-in {
+ from {
+ opacity: 0;
+ transform: translateY(16px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
}
}
-button {
- height: 40px;
- font-size: 16px;
- background: #4dabf7;
- border: 1px solid #a5d8ff;
+* {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ margin: 0;
+ color: var(--text);
+ font-family: "Avenir Next", "Segoe UI", sans-serif;
+ background: var(--bg);
+ overflow-y: scroll;
+}
+
+* {
+ outline: none;
+}
+
+body::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ background-image: linear-gradient(
+ rgba(255, 255, 255, 0.05) 1px,
+ transparent 1px
+ ),
+ linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
+ background-size: 32px 32px;
+ mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.45), transparent 80%);
+ pointer-events: none;
+ opacity: 0.35;
+}
+
+body.dark-mode::before {
+ opacity: 0.08;
+}
+
+#root {
+ position: relative;
+ min-height: 100vh;
+ padding: 24px;
+ // Reserve space for the fixed excalidraw panel on the right
+ padding-right: calc(54vw);
+}
+
+.left-side {
+ position: relative;
+ min-height: calc(100vh - 48px);
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ border-radius: var(--radius-xl);
+ overflow: visible;
+}
+
+.left-side > * {
+ position: relative;
+ z-index: 1;
+}
+
+.playground-hero {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding-right: 92px;
+}
+
+.playground-hero-copy {
+ max-width: 20rem;
+}
+
+.playground-kicker,
+.panel-kicker,
+.preview-badge,
+.testcase-type {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ margin: 0;
+ color: var(--secondary);
+ font-size: 0.75rem;
+ font-weight: 700;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+}
+
+.playground-hero h1,
+.panel-header h2,
+.testcase-name {
+ margin: 0;
+ font-family: "Trebuchet MS", "Avenir Next", "Segoe UI", sans-serif;
+ letter-spacing: -0.05em;
+}
+
+.playground-hero h1 {
+ margin-top: 8px;
+ font-size: clamp(1.55rem, 2.1vw, 2.1rem);
+ line-height: 0.96;
+}
+
+.custom-test-hint {
+ color: var(--muted);
+ line-height: 1.65;
+}
+
+.playground-doc-link {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 38px;
+ padding: 0 14px;
+ border: 1px solid var(--panel-border);
+ border-radius: 10px;
box-shadow: none;
- border-radius: 2px;
- color: white;
+ color: var(--text);
+ font-size: 0.88rem;
+ font-weight: 700;
+ text-decoration: none;
+ transition: transform 180ms ease, border-color 180ms ease,
+ box-shadow 180ms ease, color 180ms ease;
+}
+
+.playground-doc-link:hover {
+ transform: translateY(-1px);
+ border-color: var(--panel-border-strong);
+ box-shadow: 0 14px 28px rgba(29, 108, 114, 0.14);
+}
+
+body.dark-mode .playground-doc-link {
+ background: rgba(255, 255, 255, 0.03);
+}
+
+a {
+ color: inherit;
+}
+
+.playground-panel,
+#excalidraw {
+ animation: rise-in 420ms ease both;
+}
+
+.testcase-section {
+ background: var(--testcase-section);
+ overflow: visible;
+}
+
+.testcase-section summary {
+ position: sticky;
+ top: 0;
+ z-index: 4;
+ background: var(--testcase-section);
+ border-top-left-radius: var(--radius-lg);
+ border-top-right-radius: var(--radius-lg);
+}
+
+.testcase-section[open] summary {
+ box-shadow: 0 1px 0 var(--panel-border);
+}
+
+body.dark-mode .testcase-section {
+ box-shadow: inset 0 0 0 1px var(--panel-border);
+}
+
+body.dark-mode .testcase-section summary {
+ // background: linear-gradient(180deg, #3a2d27, #2f241f);
+}
+
+.panel-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.panel-header h2 {
+ margin-top: 8px;
+ font-size: clamp(1.35rem, 1.6vw, 1.7rem);
+ line-height: 1;
+}
+
+.playground-button {
+ appearance: none;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ min-height: 44px;
+ padding: 0 18px;
+ border: 1px solid rgba(255, 255, 255, 0.16);
+ border-radius: 10px;
+ background: linear-gradient(135deg, var(--accent), var(--accent-strong));
+ color: #fff8f2;
cursor: pointer;
+ font-size: 0.97rem;
+ font-weight: 700;
+ letter-spacing: -0.01em;
+ transition: transform 180ms ease, box-shadow 180ms ease, filter 180ms ease;
+}
+
+.playground-button:hover {
+ transform: translateY(-1px);
+ filter: saturate(1.04);
+}
+
+body.dark-mode .playground-button {
+ border-color: rgba(255, 255, 255, 0.08);
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.24);
+}
+
+.playground-button:active {
+ transform: translateY(0);
+}
+
+#dark-mode-toggle {
+ position: fixed;
+ top: 24px;
+ right: 24px;
+ z-index: 20;
+ min-height: 42px;
+ padding: 0 16px;
+ border-color: var(--panel-border);
+ background: rgba(35, 25, 18, 0.82);
+ box-shadow: var(--shadow-sm);
+ backdrop-filter: blur(16px);
+}
+
+body.dark-mode #dark-mode-toggle {
+ background: rgba(53, 40, 34, 0.9);
+ color: var(--text);
+ box-shadow: none;
}
#excalidraw {
- overflow: auto;
- width: 50vw;
- height: 100vh;
position: fixed;
- top: 0;
- right: 0;
+ top: 24px;
+ right: 24px;
+ bottom: 24px;
+ width: calc(54vw - 48px);
+ min-width: 420px;
+ border: 1px solid var(--panel-border);
+ border-radius: var(--radius-xl);
+ overflow: clip;
+ box-shadow: var(--shadow-lg);
+ background: radial-gradient(
+ circle at top left,
+ rgba(129, 212, 218, 0.15),
+ transparent 28%
+ ),
+ radial-gradient(
+ circle at bottom right,
+ rgba(205, 106, 62, 0.14),
+ transparent 30%
+ ),
+ linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
+}
- .mobile-misc-tools-container {
- display: none !important;
- }
+body.dark-mode #excalidraw {
+ background: linear-gradient(180deg, #342923, #231a16);
+ border-color: var(--panel-border);
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.3);
}
.excalidraw-wrapper {
height: 100%;
}
+#excalidraw .mobile-misc-tools-container {
+ display: none !important;
+}
+
+#excalidraw .excalidraw {
+ background: transparent;
+}
+
#custom-test {
- width: 50vw;
+ width: 100%;
+}
- #render-excalidraw-btn {
- margin-top: 10px;
- }
+.custom-test-form {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
- #custom-diagram {
- margin-top: 20px;
- }
+.field-label {
+ color: var(--muted);
+ font-size: 0.76rem;
+ font-weight: 700;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+}
- #parsed-data-details {
- margin-top: 20px;
- }
+#mermaid-input {
+ width: 100%;
+ min-height: 252px;
+ resize: vertical;
+ border: 1px solid rgba(255, 255, 255, 0.06);
+ border-radius: var(--radius-lg);
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent),
+ var(--code-bg);
+ color: var(--code-text);
+ font-family: "SFMono-Regular", "Consolas", monospace;
+ font-size: 0.95rem;
+ line-height: 1.7;
+ padding: 18px 20px;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
-#error {
- color: red;
+body.dark-mode #mermaid-input {
+ border-color: rgba(255, 255, 255, 0.08);
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02);
+}
+
+#mermaid-input::placeholder {
+ color: rgba(247, 234, 219, 0.58);
+}
+
+.custom-test-actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 14px;
+}
+
+.custom-test-hint {
+ margin: 0;
+ font-size: 0.94rem;
+}
+
+.custom-preview-card {
+ margin-top: 22px;
+}
+
+.preview-badge {
+ margin-bottom: 10px;
+}
+
+.custom-diagram-surface {
+ min-height: 180px;
+}
+
+#parsed-data-details {
margin-top: 20px;
}
-a {
- color: #0071ce;
+#parsed-data-details pre {
+ margin: 0 18px 18px;
+ border-radius: var(--radius-md);
+ color: var(--text);
+ font-family: "SFMono-Regular", "Consolas", monospace;
+ font-size: 0.88rem;
+ line-height: 1.6;
+ overflow: auto;
+ padding: 14px 16px;
+}
+
+#error {
+ margin: 0 18px 18px;
+ border: 1px solid rgba(207, 83, 72, 0.22);
+ border-radius: 14px;
+ background: rgba(207, 83, 72, 0.08);
+ color: #c94840;
+ padding: 12px 14px;
+}
+
+details {
+ margin-top: 18px;
+ border: 1px solid var(--panel-border);
+ border-radius: var(--radius-lg);
+ background: rgba(255, 255, 255, 0.26);
+ overflow: hidden;
+}
+
+body.dark-mode details {
+ background: rgba(255, 255, 255, 0.02);
+}
+
+details summary {
+ position: sticky;
+ top: 0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 14px 16px;
+ color: var(--text);
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 800;
+ letter-spacing: -0.02em;
+ user-select: none;
+ z-index: 2;
+}
+
+// body.dark-mode details summary {
+// background: linear-gradient(
+// 180deg,
+// rgba(54, 40, 35, 0.96),
+// rgba(39, 29, 25, 0.96)
+// );
+// }
+
+details summary::-webkit-details-marker {
+ display: none;
+}
+
+details summary::after {
+ content: "+";
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 24px;
+ height: 24px;
+ border: 1px solid var(--panel-border);
+ border-radius: 999px;
+ color: var(--muted);
+ font-size: 0.9rem;
font-weight: 600;
+}
+
+details[open] summary::after {
+ content: "−";
+}
+
+.details-summary-text {
+ min-width: 0;
+}
+
+.details-summary-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ margin-left: auto;
+}
+
+.details-summary-link {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 28px;
+ padding: 0 10px;
+ border: 1px solid var(--panel-border);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.55);
+ color: var(--secondary);
+ font-size: 0.8rem;
+ font-weight: 700;
text-decoration: none;
}
-summary {
- cursor: pointer;
- width: fit-content;
+body.dark-mode .details-summary-link {
+ background: rgba(255, 255, 255, 0.04);
+ border-color: var(--panel-border);
+}
+
+.details-summary-meta {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 28px;
+ height: 28px;
+ border-radius: 999px;
+ background: var(--accent-soft);
+ color: var(--accent-strong);
+ font-size: 0.82rem;
+ font-weight: 700;
+}
+
+body.dark-mode .details-summary-meta {
+ background: rgba(229, 139, 97, 0.18);
+ color: #f5bb96;
+}
+
+.testcase-container {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding: 0 10px;
+
+}
+
+.testcase-card {
+ margin-top: 20px;
+ padding: 10px;
+ background: var(--testcase-card-bg);
+ border: 1px solid var(--panel-border);
+ border-radius: 10px;
+}
+
+.testcase-card-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ margin-bottom: 16px;
+}
+
+.testcase-type {
+ color: var(--muted);
+}
+
+.testcase-name {
+ margin-top: 8px;
+ font-size: clamp(1.45rem, 1.8vw, 2rem);
+ line-height: 1;
+}
+
+.render-testcase-button {
+ flex-shrink: 0;
+}
+
+.testcase-codeblock {
+ position: relative;
+}
+
+.testcase-codeblock-actions {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+}
+
+.testcase-codeblock-button {
+ min-height: 30px;
+ padding: 0 12px;
+ box-shadow: none;
+ font-size: 0.83rem;
+ white-space: nowrap;
}
.testcase-container pre {
- font-size: 16px;
- font-weight: 600;
- font-style: italic;
- background: #eeeef1;
+ margin: 0;
+ border: 1px solid rgba(255, 255, 255, 0.06);
+ border-radius: 10px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent),
+ var(--code-bg);
+ color: var(--code-text);
+ font-family: "SFMono-Regular", "Consolas", monospace;
+ font-size: 0.95rem;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 1.65;
white-space: pre-wrap;
- width: 45vw;
- padding: 5px;
+ padding: 46px 16px 16px;
}
-.split {
- min-height: 100vh;
+.testcase-definition-textarea {
+ display: block;
+ width: 100%;
+ margin: 0;
+ border: 1px solid rgba(255, 255, 255, 0.06);
+ border-radius: 10px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent),
+ var(--code-bg);
+ color: var(--code-text);
+ font-family: "SFMono-Regular", "Consolas", monospace;
+ font-size: 0.95rem;
+ font-weight: 500;
+ line-height: 1.65;
+ white-space: pre;
+ padding: 16px 16px;
+ resize: vertical;
+}
+
+body.dark-mode .testcase-definition-textarea {
+ border-color: rgba(255, 255, 255, 0.08);
+}
+
+.diagram-preview-clickable {
+ cursor: pointer;
+ transition: outline-color 180ms ease, box-shadow 180ms ease;
+ outline: 2px solid transparent;
+ outline-offset: -2px;
+}
+
+.diagram-preview-clickable:hover {
+ outline-color: var(--accent);
+ box-shadow: 0 0 0 1px var(--accent);
+}
+
+.copy-mermaid-button {
+ border-color: rgba(255, 255, 255, 0.08);
+ background: linear-gradient(135deg, #275f7b, #1d485d);
+}
+
+.diagram-preview-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+ margin-top: 16px;
+ align-items: flex-start;
+}
+
+.diagram-preview-panel {
+ min-width: 0;
+ // border: 1px solid var(--panel-border);
+ border-radius: 12px;
+ // background: rgba(255, 255, 255, 0.38);
+ padding: 14px;
+}
+
+body.dark-mode .diagram-preview-panel {
+ // background: rgba(255, 255, 255, 0.03);
+}
+
+.diagram-preview-title {
+ margin: 0 0 10px;
+ color: var(--muted);
+ font-size: 0.78rem;
+ font-weight: 800;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+}
+
+.diagram-preview-surface {
+ min-height: 220px;
+ border-radius: 10px;
+ overflow: auto;
+ padding: 14px;
+}
+
+.diagram-preview-surface svg {
+ display: block;
+ max-width: 100%;
+ height: auto;
+}
+
+.preview-loading,
+.preview-error {
+ min-height: 120px;
display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12px;
+ text-align: center;
}
-.gutter-horizontal {
- background: #ccc;
- z-index: 1;
- cursor: row-resize;
+.preview-loading {
+ color: var(--muted);
+ font-size: 0.94rem;
+}
+
+.preview-error {
+ background: rgba(207, 83, 72, 0.08);
+ color: #c94840;
+ padding: 16px;
+ white-space: pre-wrap;
+}
+
+@media (max-width: 1240px) {
+ #root {
+ padding-right: 24px;
+ }
+
+ .left-side {
+ min-height: auto;
+ }
- &:hover {
- cursor: col-resize;
+ #excalidraw {
+ position: relative;
+ top: auto;
+ right: auto;
+ bottom: auto;
+ width: auto;
+ min-width: auto;
+ height: 72vh;
}
}
-.mermaid-container {
- width: 50%;
- height: max-content;
- display: "flex";
- flex-direction: "column";
- overflow: auto;
-
- .mermaid-header-container {
- width: 100%;
- display: flex;
- justify-content: space-between;
+@media (max-width: 900px) {
+ #root {
+ gap: 18px;
+ padding: 18px;
+ }
+
+ .left-side {
+ padding: 24px 20px;
+ }
+
+ .playground-hero {
+ padding-right: 64px;
+ }
+
+ .panel-header,
+ .testcase-card-header,
+ .custom-test-actions {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .diagram-preview-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 640px) {
+ #root {
+ padding: 14px;
+ }
+
+ .left-side,
+ #excalidraw,
+ .playground-panel {
+ border-radius: 14px;
+ }
+
+ .playground-hero {
+ padding-right: 0;
+ }
+
+ .playground-hero h1 {
+ max-width: none;
+ }
+
+ #dark-mode-toggle {
+ top: 14px;
+ right: 14px;
+ }
+
+ .testcase-container {
+ padding: 0 12px 12px;
+ }
+
+ .testcase-card {
+ padding: 18px;
}
}
diff --git a/playground/testcases/class.ts b/playground/testcases/class.ts
index dbee0b52..b8343f16 100644
--- a/playground/testcases/class.ts
+++ b/playground/testcases/class.ts
@@ -265,4 +265,52 @@ export const CLASS_DIAGRAM_TESTCASES: TestCase[] = [
Baz <|-- Foo
`,
},
+ {
+ type: "class",
+ name: "Class with Custom Colors using style",
+ definition: `classDiagram
+ class Animal
+ class Mineral
+ style Animal fill:#f9f,stroke:#333,stroke-width:4px
+ style Mineral fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5`,
+ },
+ {
+ type: "class",
+ name: "Styled Class Text Colors",
+ definition: `classDiagram
+ class User {
+ +String username
+ +String email
+ +login()
+ +logout()
+ }
+
+ class Profile {
+ +String bio
+ +String avatarUrl
+ +updateProfile()
+ }
+
+ class Post {
+ +int id
+ +String content
+ +DateTime createdAt
+ +publish()
+ }
+
+ class Comment {
+ +String text
+ +submit()
+ }
+
+ User "1" -- "1" Profile : has
+ User "1" -- "*" Post : creates
+ Post "1" -- "*" Comment : contains
+ User "1" -- "*" Comment : writes
+
+ style User fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#01579b
+ style Profile fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#4a148c
+ style Post fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px,color:#1b5e20
+ style Comment fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#e65100`,
+ },
];
diff --git a/playground/testcases/er.ts b/playground/testcases/er.ts
new file mode 100644
index 00000000..baa628b4
--- /dev/null
+++ b/playground/testcases/er.ts
@@ -0,0 +1,163 @@
+import type { TestCase } from "../SingleTestCase";
+
+export const ERD_DIAGRAM_TESTCASES: TestCase[] = [
+ {
+ type: "erd",
+ name: "Single Entity",
+ definition: `erDiagram
+ "This ❤ Unicode"`,
+ },
+ {
+ type: "erd",
+ name: "Basic Cardinalities",
+ definition: `erDiagram
+ PROPERTY ||--|{ ROOM : contains`,
+ },
+ {
+ type: "erd",
+ name: "Self Relationship",
+ definition: `erDiagram
+ CATEGORY ||--o{ CATEGORY : parent_of
+ CATEGORY {
+ integer id PK
+ integer parent_id FK
+ string display_name
+ }`,
+ },
+ {
+ type: "erd",
+ name: "Identifying and Non-Identifying Relationships",
+ definition: `erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows
+ PERSON }o..o{ NAMED-DRIVER : is`,
+ },
+ {
+ type: "erd",
+ name: "Entity Aliases",
+ definition: `erDiagram
+ p[Person] {
+ string firstName
+ string lastName
+ }
+ a["Customer Account"] {
+ string email
+ }
+ p ||--o| a : has`,
+ },
+ {
+ type: "erd",
+ name: "Attributes with Keys and Comments",
+ definition: `erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows
+ CAR {
+ string registrationNumber PK
+ string make
+ string model
+ string[] parts
+ }
+ PERSON ||--o{ NAMED-DRIVER : is
+ PERSON {
+ string driversLicense PK "The license #"
+ string(99) firstName "Only 99 characters are allowed"
+ string lastName
+ string phone UK
+ int age
+ }
+ NAMED-DRIVER {
+ string carRegistrationNumber PK, FK
+ string driverLicence PK, FK
+ }
+ MANUFACTURER only one to zero or more CAR : makes`,
+ },
+ {
+ type: "erd",
+ name: "Direction Left to Right",
+ definition: `erDiagram
+ direction LR
+ CUSTOMER ||--o{ ORDER : places
+ CUSTOMER {
+ string name
+ string custNumber
+ string sector
+ }
+ ORDER ||--|{ LINE-ITEM : contains
+ ORDER {
+ int orderNumber
+ string deliveryAddress
+ }
+ LINE-ITEM {
+ string productCode
+ int quantity
+ float pricePerUnit
+ }`,
+ },
+ {
+ type: "erd",
+ name: "Class Styling",
+ definition: `erDiagram
+ CAR {
+ string registrationNumber
+ string make
+ string model
+ }
+ PERSON {
+ string firstName
+ string lastName
+ int age
+ }
+ PERSON:::foo ||--|| CAR : owns
+ PERSON o{--|| HOUSE:::bar : has
+ classDef default fill:#f9f,stroke-width:4px
+ classDef foo stroke:#f00
+ classDef bar stroke:#0f0`,
+ },
+ {
+ type: "erd",
+ name: "Styled ERD Text Colors",
+ definition: `erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ ORDER ||--|{ LINE-ITEM : contains
+ PRODUCT ||--o{ LINE-ITEM : included-in
+ CATEGORY ||--o{ PRODUCT : categorizes
+
+ CUSTOMER {
+ int customer_id PK
+ string first_name
+ string last_name
+ string email
+ string phone
+ }
+ ORDER {
+ int order_id PK
+ int customer_id FK
+ datetime order_date
+ string status
+ decimal total_amount
+ }
+ LINE-ITEM {
+ int line_item_id PK
+ int order_id FK
+ int product_id FK
+ int quantity
+ decimal unit_price
+ }
+ PRODUCT {
+ int product_id PK
+ string name
+ string sku
+ decimal price
+ int stock_quantity
+ }
+ CATEGORY {
+ int category_id PK
+ string name
+ string description
+ }
+
+ style CUSTOMER fill:#2d3436,stroke:#00cec9,stroke-width:2px,color:#00cec9
+ style ORDER fill:#2d3436,stroke:#0984e3,stroke-width:2px,color:#0984e3
+ style LINE-ITEM fill:#2d3436,stroke:#6c5ce7,stroke-width:2px,color:#6c5ce7
+ style PRODUCT fill:#2d3436,stroke:#e17055,stroke-width:2px,color:#e17055
+ style CATEGORY fill:#2d3436,stroke:#fdcb6e,stroke-width:2px,color:#fdcb6e`,
+ },
+];
diff --git a/playground/testcases/flowchart.ts b/playground/testcases/flowchart.ts
index a2981e8d..937af451 100644
--- a/playground/testcases/flowchart.ts
+++ b/playground/testcases/flowchart.ts
@@ -369,6 +369,24 @@ style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5
classDef foobar stroke:#00f stroke-width:2px`,
type: "flowchart",
},
+ {
+ name: "Styling subgraphs",
+ definition: `graph TD
+ %% Node styles work fine
+ classDef criticalNode fill:#ff5252,stroke:#b71c1c,color:#fff
+
+ subgraph SG_RED ["CRITICAL MODULE"]
+ Node01["Important Service"]:::criticalNode
+ end
+
+ subgraph SG_BLUE ["DASHED BORDER MODULE"]
+ Node02["Feature Module"]
+ end
+
+ style SG_RED fill:#ffebee,stroke:#c62828,stroke-width:3px
+ style SG_BLUE fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,stroke-dasharray: 10 5`,
+ type: "flowchart",
+ },
{
name: "Classes",
definition: `flowchart LR
diff --git a/playground/testcases/unsupported.ts b/playground/testcases/unsupported.ts
index bea9c252..4a2d0c40 100644
--- a/playground/testcases/unsupported.ts
+++ b/playground/testcases/unsupported.ts
@@ -1,14 +1,6 @@
import type { TestCase } from "../SingleTestCase";
export const UNSUPPORTED_DIAGRAM_TESTCASES: TestCase[] = [
- {
- type: "unsupported",
- name: "ER Diagram",
- definition: `erDiagram
- CUSTOMER ||--o{ ORDER : places
- ORDER ||--|{ LINE-ITEM : contains
- CUSTOMER }|..|{ DELIVERY-ADDRESS : uses`,
- },
{
type: "unsupported",
name: "Gantt Diagram",
diff --git a/playground/usePersistedSectionState.ts b/playground/usePersistedSectionState.ts
new file mode 100644
index 00000000..0ed404f8
--- /dev/null
+++ b/playground/usePersistedSectionState.ts
@@ -0,0 +1,70 @@
+import { useCallback, useState, type SyntheticEvent } from "react";
+
+const STORAGE_KEY = "mermaid-to-excalidraw-playground-sections";
+
+type PersistedSectionState = Record;
+
+const readPersistedSectionState = (): PersistedSectionState => {
+ if (typeof window === "undefined") {
+ return {};
+ }
+
+ try {
+ const storedState = window.localStorage.getItem(STORAGE_KEY);
+ if (!storedState) {
+ return {};
+ }
+
+ const parsedState = JSON.parse(storedState);
+ if (!parsedState || typeof parsedState !== "object") {
+ return {};
+ }
+
+ return Object.fromEntries(
+ Object.entries(parsedState).filter(
+ (entry): entry is [string, boolean] => typeof entry[1] === "boolean"
+ )
+ );
+ } catch (error) {
+ console.error("Failed to read section state from localStorage:", error);
+ return {};
+ }
+};
+
+const writePersistedSectionState = (state: PersistedSectionState) => {
+ if (typeof window === "undefined") {
+ return;
+ }
+
+ try {
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+ } catch (error) {
+ console.error("Failed to save section state to localStorage:", error);
+ }
+};
+
+export const usePersistedSectionState = (
+ sectionId: string,
+ defaultExpanded = false
+) => {
+ const [isExpanded, setIsExpanded] = useState(() => {
+ const persistedState = readPersistedSectionState();
+ return persistedState[sectionId] ?? defaultExpanded;
+ });
+
+ const handleToggle = useCallback(
+ (event: SyntheticEvent) => {
+ const nextExpanded = event.currentTarget.open;
+ setIsExpanded(nextExpanded);
+
+ const persistedState = readPersistedSectionState();
+ writePersistedSectionState({
+ ...persistedState,
+ [sectionId]: nextExpanded,
+ });
+ },
+ [sectionId]
+ );
+
+ return { isExpanded, handleToggle };
+};
diff --git a/playground/vite.config.ts b/playground/vite.config.ts
index a40259cc..78a9b59a 100644
--- a/playground/vite.config.ts
+++ b/playground/vite.config.ts
@@ -2,18 +2,25 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
export default defineConfig({
+ resolve: {
+ dedupe: ["react", "react-dom", "react/jsx-runtime"],
+ },
build: {
outDir: "../public",
emptyOutDir: true,
assetsDir: "./",
+ minify: false,
+ sourcemap: true,
},
define: {
"process.env.IS_PREACT": JSON.stringify("false"),
},
plugins: [react()],
server: {
+ port: 3418,
+ open: true,
warmup: {
- /*
+ /*
A small performance improvement so that this file is already transformed, cached when we receive the request :)
See more: https://vitejs.dev/guide/performance.html#warm-up-frequently-used-files
*/
@@ -25,4 +32,8 @@ export default defineConfig({
],
},
},
+ // Enable source maps in dev mode
+ esbuild: {
+ sourcemap: true,
+ },
});
diff --git a/src/constants.ts b/src/constants.ts
index 28d868f2..90860a02 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -10,9 +10,8 @@ export const MERMAID_CONFIG = {
startOnLoad: false,
flowchart: { curve: "linear" },
themeVariables: {
- // Multiplying by 1.25 to increase the font size by 25% and render correctly in Excalidraw
- fontSize: `${DEFAULT_FONT_SIZE * 1.25}px`,
+ fontSize: `${DEFAULT_FONT_SIZE}px`,
},
maxEdges: 500,
maxTextSize: 50000,
-};
+} as const;
diff --git a/src/converter/helpers.ts b/src/converter/helpers.ts
index 8b16aae5..97c5e895 100644
--- a/src/converter/helpers.ts
+++ b/src/converter/helpers.ts
@@ -1,15 +1,15 @@
+import type { ExcalidrawTextElement } from "@excalidraw/excalidraw/element/types";
+import type { Arrowhead } from "@excalidraw/excalidraw/element/types";
import {
- Arrowhead,
- ExcalidrawTextElement,
-} from "@excalidraw/excalidraw/types/element/types.js";
-import {
+ ContainerStyle,
CONTAINER_STYLE_PROPERTY,
LABEL_STYLE_PROPERTY,
+ LabelStyle,
SubGraph,
Vertex,
} from "../interfaces.js";
import { ExcalidrawVertexElement } from "../types.js";
-import { Mutable } from "@excalidraw/excalidraw/types/utility-types.js";
+import type { Mutable } from "@excalidraw/excalidraw/common/utility-types";
import { removeMarkdown } from "@excalidraw/markdown-to-text";
import { Edge } from "../parser/flowchart.js";
@@ -25,7 +25,7 @@ export interface ArrowType {
*/
const MERMAID_EDGE_TYPE_MAPPER: { [key: string]: ArrowType } = {
arrow_circle: {
- endArrowhead: "dot",
+ endArrowhead: "circle",
},
arrow_cross: {
endArrowhead: "bar",
@@ -35,8 +35,8 @@ const MERMAID_EDGE_TYPE_MAPPER: { [key: string]: ArrowType } = {
startArrowhead: null,
},
double_arrow_circle: {
- endArrowhead: "dot",
- startArrowhead: "dot",
+ endArrowhead: "circle",
+ startArrowhead: "circle",
},
double_arrow_cross: {
endArrowhead: "bar",
@@ -76,7 +76,7 @@ const removeFontAwesomeIcons = (input: string): string => {
* Compute style for vertex
*/
export const computeExcalidrawVertexStyle = (
- style: Vertex["containerStyle"]
+ style: ContainerStyle
): Partial> => {
const excalidrawProperty: Partial> = {};
Object.keys(style).forEach((property) => {
@@ -109,7 +109,7 @@ export const computeExcalidrawVertexStyle = (
* Compute style for label
*/
export const computeExcalidrawVertexLabelStyle = (
- style: Vertex["labelStyle"]
+ style: LabelStyle
): Partial> => {
const excalidrawProperty: Partial> = {};
Object.keys(style).forEach((property) => {
diff --git a/src/converter/transformToExcalidrawSkeleton.ts b/src/converter/transformToExcalidrawSkeleton.ts
index 277aac97..29fb41d9 100644
--- a/src/converter/transformToExcalidrawSkeleton.ts
+++ b/src/converter/transformToExcalidrawSkeleton.ts
@@ -1,6 +1,13 @@
-import { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/types/data/transform.js";
+import type {
+ ExcalidrawElementSkeleton,
+ ValidContainer,
+ ValidLinearElement,
+} from "@excalidraw/excalidraw/element/transform";
+import type { LocalPoint } from "@excalidraw/excalidraw/math/types";
import { Arrow, Line, Node, Text } from "../elementSkeleton.js";
+const point = (x: number, y: number) => [x, y] as LocalPoint;
+
export const normalizeText = (text: string) => {
return text.replace(/\\n/g, "\n");
};
@@ -11,8 +18,8 @@ export const transformToExcalidrawLineSkeleton = (line: Line) => {
x: line.startX,
y: line.startY,
points: [
- [0, 0],
- [line.endX - line.startX, line.endY - line.startY],
+ point(0, 0),
+ point(line.endX - line.startX, line.endY - line.startY),
],
width: line.endX - line.startX,
height: line.endY - line.startY,
@@ -38,7 +45,8 @@ export const transformToExcalidrawTextSkeleton = (element: Text) => {
height: element.height,
text: normalizeText(element.text) || "",
fontSize: element.fontSize,
- verticalAlign: "middle",
+ verticalAlign: "top",
+ strokeColor: element.color,
};
if (element.groupId) {
Object.assign(textElement, { groupIds: [element.groupId] });
@@ -52,6 +60,15 @@ export const transformToExcalidrawTextSkeleton = (element: Text) => {
export const transformToExcalidrawContainerSkeleton = (
element: Exclude
) => {
+ const label = {
+ text: normalizeText(element?.label?.text || ""),
+ fontSize: element?.label?.fontSize,
+ textAlign: element.label?.textAlign,
+ verticalAlign: element.label?.verticalAlign || "middle",
+ strokeColor: element.label?.color || "#000",
+ ...(element.groupId ? { groupIds: [element.groupId] } : {}),
+ } as NonNullable;
+
let extraProps = {};
if (element.type === "rectangle" && element.subtype === "activation") {
extraProps = {
@@ -59,20 +76,14 @@ export const transformToExcalidrawContainerSkeleton = (
fillStyle: "solid",
};
}
- const container: ExcalidrawElementSkeleton = {
+ const container: ValidContainer = {
id: element.id,
type: element.type,
x: element.x,
y: element.y,
width: element.width,
height: element.height,
- label: {
- text: normalizeText(element?.label?.text || ""),
- fontSize: element?.label?.fontSize,
- verticalAlign: element.label?.verticalAlign || "middle",
- strokeColor: element.label?.color || "#000",
- groupIds: element.groupId ? [element.groupId] : [],
- },
+ label,
strokeStyle: element?.strokeStyle,
strokeWidth: element?.strokeWidth,
strokeColor: element?.strokeColor,
@@ -88,22 +99,24 @@ export const transformToExcalidrawContainerSkeleton = (
};
export const transformToExcalidrawArrowSkeleton = (arrow: Arrow) => {
- const arrowElement: ExcalidrawElementSkeleton = {
+ const arrowElement: ValidLinearElement = {
type: "arrow",
x: arrow.startX,
y: arrow.startY,
- points: arrow.points || [
- [0, 0],
- [arrow.endX - arrow.startX, arrow.endY - arrow.startY],
+ points: arrow.points?.map(([x, y]) => point(x, y)) || [
+ point(0, 0),
+ point(arrow.endX - arrow.startX, arrow.endY - arrow.startY),
],
width: arrow.endX - arrow.startX,
height: arrow.endY - arrow.startY,
strokeStyle: arrow?.strokeStyle || "solid",
- endArrowhead: arrow?.endArrowhead || null,
- startArrowhead: arrow?.startArrowhead || null,
+ endArrowhead: (arrow?.endArrowhead || null) as any,
+ startArrowhead: (arrow?.startArrowhead || null) as any,
label: {
text: normalizeText(arrow?.label?.text || ""),
fontSize: 16,
+ textAlign: arrow?.label?.textAlign,
+ verticalAlign: arrow?.label?.verticalAlign,
},
roundness: {
type: 2,
diff --git a/src/converter/types/class.ts b/src/converter/types/class.ts
index 9819056a..bc252d4d 100644
--- a/src/converter/types/class.ts
+++ b/src/converter/types/class.ts
@@ -7,14 +7,14 @@ import {
} from "../transformToExcalidrawSkeleton.js";
import { GraphConverter } from "../GraphConverter.js";
-import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/types/data/transform.js";
+import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/element/transform";
import type { Class } from "../../parser/class.js";
export const classToExcalidrawSkeletonConvertor = new GraphConverter({
converter: (chart: Class) => {
const elements: ExcalidrawElementSkeleton[] = [];
- Object.values(chart.nodes).forEach((node) => {
+ chart.nodes.forEach((node) => {
if (!node || !node.length) {
return;
}
diff --git a/src/converter/types/er.ts b/src/converter/types/er.ts
new file mode 100644
index 00000000..3405d742
--- /dev/null
+++ b/src/converter/types/er.ts
@@ -0,0 +1,57 @@
+import {
+ transformToExcalidrawArrowSkeleton,
+ transformToExcalidrawContainerSkeleton,
+ transformToExcalidrawLineSkeleton,
+ transformToExcalidrawTextSkeleton,
+} from "../transformToExcalidrawSkeleton.js";
+import { GraphConverter } from "../GraphConverter.js";
+
+import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/element/transform";
+import type { ERD } from "../../parser/er.js";
+
+export const erToExcalidrawSkeletonConvertor = new GraphConverter({
+ converter: (chart: ERD) => {
+ const elements: ExcalidrawElementSkeleton[] = [];
+
+ chart.nodes.forEach((node) => {
+ if (!node || !node.length) {
+ return;
+ }
+
+ node.forEach((element) => {
+ let excalidrawElement: ExcalidrawElementSkeleton;
+
+ switch (element.type) {
+ case "line":
+ excalidrawElement = transformToExcalidrawLineSkeleton(element);
+ break;
+ case "rectangle":
+ case "ellipse":
+ excalidrawElement = transformToExcalidrawContainerSkeleton(element);
+ break;
+ case "text":
+ excalidrawElement = transformToExcalidrawTextSkeleton(element);
+ break;
+ default:
+ throw `unknown type ${element.type}`;
+ }
+
+ elements.push(excalidrawElement);
+ });
+ });
+
+ chart.lines.forEach((line) => {
+ elements.push(transformToExcalidrawLineSkeleton(line));
+ });
+
+ chart.arrows.forEach((arrow) => {
+ elements.push(transformToExcalidrawArrowSkeleton(arrow));
+ });
+
+ chart.text.forEach((textElement) => {
+ elements.push(transformToExcalidrawTextSkeleton(textElement));
+ });
+
+ return { elements };
+ },
+});
diff --git a/src/converter/types/flowchart.ts b/src/converter/types/flowchart.ts
index dbb32440..bf38a69d 100644
--- a/src/converter/types/flowchart.ts
+++ b/src/converter/types/flowchart.ts
@@ -1,5 +1,11 @@
import { GraphConverter } from "../GraphConverter.js";
-import { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/types/data/transform.js";
+import type {
+ ExcalidrawElementSkeleton,
+ ValidLinearElement,
+} from "@excalidraw/excalidraw/element/transform";
+import type { LocalPoint } from "@excalidraw/excalidraw/math/types";
+
+const localPoint = (x: number, y: number) => [x, y] as LocalPoint;
import {
getText,
@@ -9,6 +15,42 @@ import {
} from "../helpers.js";
import { VERTEX_TYPE } from "../../interfaces.js";
import { Flowchart } from "../../parser/flowchart.js";
+import { DEFAULT_FONT_SIZE } from "../../constants.js";
+
+const SUBGRAPH_LABEL_HORIZONTAL_PADDING = 32;
+const SUBGRAPH_LABEL_WIDTH_RATIO = 0.62;
+const VERTEX_LABEL_HORIZONTAL_PADDING = 12;
+const MIN_VERTEX_LABEL_FONT_SIZE = 12;
+
+const estimateLabelWidth = (text: string, fontSize: number) => {
+ return Math.max(
+ 20,
+ Math.ceil(text.length * fontSize * SUBGRAPH_LABEL_WIDTH_RATIO)
+ );
+};
+
+const computeVertexLabelFontSize = (
+ vertexType: string,
+ text: string,
+ width: number,
+ fontSize?: number
+) => {
+ const safeFontSize = fontSize || DEFAULT_FONT_SIZE;
+ if ((vertexType !== VERTEX_TYPE.CYLINDER) || !text || text.includes("\n")) {
+ return safeFontSize;
+ }
+
+ const availableWidth = Math.max(20, width - VERTEX_LABEL_HORIZONTAL_PADDING);
+ const estimatedWidth = estimateLabelWidth(text, safeFontSize);
+ if (estimatedWidth <= availableWidth) {
+ return safeFontSize;
+ }
+
+ return Math.max(
+ MIN_VERTEX_LABEL_FONT_SIZE,
+ Math.floor(availableWidth / (text.length * SUBGRAPH_LABEL_WIDTH_RATIO))
+ );
+};
const computeGroupIds = (
graph: Flowchart
@@ -84,21 +126,35 @@ export const FlowchartToExcalidrawSkeletonConverter = new GraphConverter({
// SubGraphs
graph.subGraphs.reverse().forEach((subGraph) => {
const groupIds = getGroupIds(subGraph.id);
+ const subGraphText = getText(subGraph);
+ const safeFontSize = fontSize || 16;
+ const estimatedTextWidth = estimateLabelWidth(subGraphText, safeFontSize);
+ const minSubGraphWidth =
+ estimatedTextWidth + SUBGRAPH_LABEL_HORIZONTAL_PADDING * 2;
+ const width = Math.max(subGraph.width, minSubGraphWidth);
+ const x = subGraph.x - (width - subGraph.width) / 2;
+
+ const containerStyle = computeExcalidrawVertexStyle(
+ subGraph.containerStyle
+ );
+ const labelStyle = computeExcalidrawVertexLabelStyle(subGraph.labelStyle);
const containerElement: ExcalidrawElementSkeleton = {
id: subGraph.id,
type: "rectangle",
groupIds,
- x: subGraph.x,
+ x,
y: subGraph.y,
- width: subGraph.width,
+ width,
height: subGraph.height,
label: {
groupIds,
- text: getText(subGraph),
+ text: subGraphText,
fontSize,
verticalAlign: "top",
+ ...labelStyle,
},
+ ...containerStyle,
};
elements.push(containerElement);
@@ -110,6 +166,13 @@ export const FlowchartToExcalidrawSkeletonConverter = new GraphConverter({
return;
}
const groupIds = getGroupIds(vertex.id);
+ const vertexText = getText(vertex);
+ const vertexLabelFontSize = computeVertexLabelFontSize(
+ vertex.type,
+ vertexText,
+ vertex.width,
+ fontSize
+ );
// Compute custom style
const containerStyle = computeExcalidrawVertexStyle(
@@ -128,8 +191,8 @@ export const FlowchartToExcalidrawSkeletonConverter = new GraphConverter({
strokeWidth: 2,
label: {
groupIds,
- text: getText(vertex),
- fontSize,
+ text: vertexText,
+ fontSize: vertexLabelFontSize,
...labelStyle,
},
link: vertex.link || null,
@@ -161,8 +224,9 @@ export const FlowchartToExcalidrawSkeletonConverter = new GraphConverter({
roundness: { type: 3 },
label: {
groupIds,
- text: getText(vertex),
- fontSize,
+ text: vertexText,
+ fontSize: vertexLabelFontSize,
+ ...labelStyle,
},
};
containerElement = { ...containerElement, groupIds, type: "ellipse" };
@@ -195,16 +259,25 @@ export const FlowchartToExcalidrawSkeletonConverter = new GraphConverter({
const { startX, startY, reflectionPoints } = edge;
// Calculate Excalidraw arrow's points
- const points = reflectionPoints.map((point) => [
- point.x - reflectionPoints[0].x,
- point.y - reflectionPoints[0].y,
- ]);
+ const points = reflectionPoints.map((point) =>
+ localPoint(
+ point.x - reflectionPoints[0].x,
+ point.y - reflectionPoints[0].y
+ )
+ );
// Get supported arrow type
- const arrowType = computeExcalidrawArrowType(edge.type);
+ const arrowType = computeExcalidrawArrowType(edge.type || "arrow_point");
+
+ // Bind start and end vertex to arrow
+ const startVertex = elements.find((e) => e.id === edge.start);
+ const endVertex = elements.find((e) => e.id === edge.end);
+ if (!startVertex || !endVertex) {
+ return;
+ }
const arrowId = `${edge.start}_${edge.end}`;
- const containerElement: ExcalidrawElementSkeleton = {
+ const containerElement: ValidLinearElement = {
id: arrowId,
type: "arrow",
groupIds,
@@ -222,20 +295,12 @@ export const FlowchartToExcalidrawSkeletonConverter = new GraphConverter({
type: 2,
},
...arrowType,
- };
-
- // Bind start and end vertex to arrow
- const startVertex = elements.find((e) => e.id === edge.start);
- const endVertex = elements.find((e) => e.id === edge.end);
- if (!startVertex || !endVertex) {
- return;
- }
-
- containerElement.start = {
- id: startVertex.id || "",
- };
- containerElement.end = {
- id: endVertex.id || "",
+ start: {
+ id: startVertex.id || "",
+ },
+ end: {
+ id: endVertex.id || "",
+ },
};
elements.push(containerElement);
diff --git a/src/converter/types/graphImage.ts b/src/converter/types/graphImage.ts
index a3fe5d5a..097cfc23 100644
--- a/src/converter/types/graphImage.ts
+++ b/src/converter/types/graphImage.ts
@@ -1,7 +1,7 @@
import { GraphConverter } from "../GraphConverter.js";
-import { FileId } from "@excalidraw/excalidraw/types/element/types.js";
-import { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/types/data/transform.js";
-import { BinaryFiles } from "@excalidraw/excalidraw/types/types.js";
+import type { FileId } from "@excalidraw/excalidraw/element/types";
+import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/element/transform";
+import type { BinaryFiles } from "@excalidraw/excalidraw/types";
import { nanoid } from "nanoid";
import { GraphImage } from "../../interfaces.js";
diff --git a/src/converter/types/sequence.ts b/src/converter/types/sequence.ts
index bc0c17f2..006608bf 100644
--- a/src/converter/types/sequence.ts
+++ b/src/converter/types/sequence.ts
@@ -1,4 +1,4 @@
-import { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/types/data/transform.js";
+import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/element/transform";
import { nanoid } from "nanoid";
import { GraphConverter } from "../GraphConverter.js";
@@ -10,7 +10,48 @@ import {
transformToExcalidrawArrowSkeleton,
} from "../transformToExcalidrawSkeleton.js";
-import type { ExcalidrawElement } from "../../types.js";
+const GROUP_RECT_PADDING = 10;
+const GROUP_LABEL_FONT_SIZE = 16;
+const GROUP_LABEL_VERTICAL_OFFSET = 24;
+const GROUP_LABEL_HORIZONTAL_OFFSET = 4;
+
+const isTransparentFill = (fill?: string) => {
+ if (!fill) {
+ return true;
+ }
+ const normalizedFill = fill.trim().toLowerCase();
+ return (
+ normalizedFill === "transparent" ||
+ normalizedFill === "none" ||
+ normalizedFill === "rgba(0,0,0,0)" ||
+ normalizedFill === "rgba(0, 0, 0, 0)"
+ );
+};
+
+const estimateTextWidth = (text: string, fontSize: number) => {
+ return Math.max(20, Math.round(text.length * fontSize * 0.6));
+};
+
+const addGroupId = (
+ element: ExcalidrawElementSkeleton,
+ groupId: string,
+ includeLabel = true
+) => {
+ const mutableElement = element as any;
+ const existingGroupIds = mutableElement.groupIds ?? [];
+ if (!existingGroupIds.includes(groupId)) {
+ mutableElement.groupIds = [...existingGroupIds, groupId];
+ }
+
+ if (!includeLabel || !mutableElement.label) {
+ return;
+ }
+
+ const labelGroupIds = mutableElement.label.groupIds ?? [];
+ if (!labelGroupIds.includes(groupId)) {
+ mutableElement.label.groupIds = [...labelGroupIds, groupId];
+ }
+};
export const SequenceToExcalidrawSkeletonConvertor = new GraphConverter({
converter: (chart: Sequence) => {
@@ -99,7 +140,12 @@ export const SequenceToExcalidrawSkeletonConvertor = new GraphConverter({
const id = ele.id.substring(0, hyphenIndex);
return actorKeys.includes(id);
}
+ return false;
});
+ if (!actors.length) {
+ return;
+ }
+
actors.forEach((actor) => {
if (
actor.x === undefined ||
@@ -107,36 +153,45 @@ export const SequenceToExcalidrawSkeletonConvertor = new GraphConverter({
actor.width === undefined ||
actor.height === undefined
) {
- throw new Error(`Actor attributes missing ${actor}`);
+ return;
}
minX = Math.min(minX, actor.x);
minY = Math.min(minY, actor.y);
maxX = Math.max(maxX, actor.x + actor.width);
maxY = Math.max(maxY, actor.y + actor.height);
});
- // Draw the outer rectangle enclosing the group elements
- const PADDING = 10;
- const groupRectX = minX - PADDING;
- const groupRectY = minY - PADDING;
- const groupRectWidth = maxX - minX + PADDING * 2;
- const groupRectHeight = maxY - minY + PADDING * 2;
+ if (
+ !Number.isFinite(minX) ||
+ !Number.isFinite(minY) ||
+ !Number.isFinite(maxX) ||
+ !Number.isFinite(maxY)
+ ) {
+ return;
+ }
+
+ // Draw a plain rectangle that hugs the grouped content.
+ const groupRectX = minX - GROUP_RECT_PADDING;
+ const groupRectY = minY - GROUP_RECT_PADDING;
+ const groupRectWidth = maxX - minX + GROUP_RECT_PADDING * 2;
+ const groupRectHeight = maxY - minY + GROUP_RECT_PADDING * 2;
const groupRectId = nanoid();
+ const groupId = nanoid();
const groupRect = transformToExcalidrawContainerSkeleton({
type: "rectangle",
x: groupRectX,
y: groupRectY,
width: groupRectWidth,
height: groupRectHeight,
- bgColor: group.fill,
+ bgColor: isTransparentFill(group.fill) ? undefined : group.fill,
+ strokeColor: "#1f1f1f",
+ strokeWidth: 1,
id: groupRectId,
+ groupId,
});
elements.unshift(groupRect);
- const frameId = nanoid();
-
- const frameChildren: ExcalidrawElement["id"][] = [groupRectId];
elements.forEach((ele) => {
- if (ele.type === "frame") {
+ if (ele.id === groupRectId) {
return;
}
if (
@@ -145,7 +200,7 @@ export const SequenceToExcalidrawSkeletonConvertor = new GraphConverter({
ele.width === undefined ||
ele.height === undefined
) {
- throw new Error(`Element attributes missing ${ele}`);
+ return;
}
if (
ele.x >= minX &&
@@ -153,21 +208,26 @@ export const SequenceToExcalidrawSkeletonConvertor = new GraphConverter({
ele.y >= minY &&
ele.y + ele.height <= maxY
) {
- const elementId = ele.id || nanoid();
- if (!ele.id) {
- Object.assign(ele, { id: elementId });
- }
- frameChildren.push(elementId);
+ addGroupId(ele, groupId);
}
});
- const frame: ExcalidrawElementSkeleton = {
- type: "frame",
- id: frameId,
- name,
- children: frameChildren,
- };
- elements.push(frame);
+ if (name) {
+ const groupLabel = transformToExcalidrawTextSkeleton({
+ type: "text",
+ id: nanoid(),
+ text: name,
+ x: groupRectX + GROUP_LABEL_HORIZONTAL_OFFSET,
+ y: groupRectY - GROUP_LABEL_VERTICAL_OFFSET,
+ width: estimateTextWidth(name, GROUP_LABEL_FONT_SIZE),
+ height: GROUP_LABEL_FONT_SIZE + 8,
+ fontSize: GROUP_LABEL_FONT_SIZE,
+ color: "#1f1f1f",
+ groupId,
+ });
+ addGroupId(groupLabel, groupId, false);
+ elements.push(groupLabel);
+ }
});
}
return { elements };
diff --git a/src/elementSkeleton.ts b/src/elementSkeleton.ts
index 5a40ede9..2c35c8e0 100644
--- a/src/elementSkeleton.ts
+++ b/src/elementSkeleton.ts
@@ -1,20 +1,23 @@
-import { ExcalidrawTextElement } from "@excalidraw/excalidraw/types/element/types.js";
+import type { ExcalidrawTextElement } from "@excalidraw/excalidraw/element/types";
import { entityCodesToText } from "./utils.js";
-import { ValidLinearElement } from "@excalidraw/excalidraw/types/data/transform.js";
+import type { ValidLinearElement } from "@excalidraw/excalidraw/element/transform";
import { DEFAULT_FONT_SIZE } from "./constants.js";
+import { cleanCSSValue, resolveElementTextColor } from "./parser/cssUtils.js";
export type Arrow = Omit & {
type: "arrow";
label?: {
text: string | null;
fontSize?: number;
+ textAlign?: ExcalidrawTextElement["textAlign"];
+ verticalAlign?: ExcalidrawTextElement["verticalAlign"];
};
strokeStyle?: ValidLinearElement["strokeStyle"] | null;
strokeWidth?: ValidLinearElement["strokeWidth"];
- points?: number[][];
+ points?: readonly [number, number][];
sequenceNumber?: Container;
- startArrowhead?: ValidLinearElement["startArrowhead"];
- endArrowhead?: ValidLinearElement["endArrowhead"];
+ startArrowhead?: SupportedArrowhead;
+ endArrowhead?: SupportedArrowhead;
start?: ValidLinearElement["start"];
end?: ValidLinearElement["end"];
};
@@ -42,6 +45,7 @@ export type Text = {
width?: number;
height?: number;
fontSize: number;
+ color?: string;
groupId?: string;
metadata?: { [key: string]: any };
};
@@ -55,6 +59,7 @@ export type Container = {
text: string | null;
fontSize: number;
color?: string;
+ textAlign?: ExcalidrawTextElement["textAlign"];
verticalAlign?: ExcalidrawTextElement["verticalAlign"];
};
width?: number;
@@ -70,13 +75,25 @@ export type Container = {
export type Node = Container | Line | Arrow | Text;
+export type CardinalityArrowhead =
+ | "cardinality_one"
+ | "cardinality_many"
+ | "cardinality_one_or_many"
+ | "cardinality_exactly_one"
+ | "cardinality_zero_or_one"
+ | "cardinality_zero_or_many";
+
+export type SupportedArrowhead =
+ | ValidLinearElement["startArrowhead"]
+ | CardinalityArrowhead;
+
export const createArrowSkeletonFromSVG = (
arrowNode: SVGLineElement | SVGPathElement,
opts?: {
label?: string;
strokeStyle?: ValidLinearElement["strokeStyle"];
- startArrowhead?: ValidLinearElement["startArrowhead"];
- endArrowhead?: ValidLinearElement["endArrowhead"];
+ startArrowhead?: SupportedArrowhead;
+ endArrowhead?: SupportedArrowhead;
}
) => {
const arrow = {} as Arrow;
@@ -102,9 +119,9 @@ export const createArrowSkeletonFromSVG = (
.split(",")
.map((coord) => parseFloat(coord));
- const points: Arrow["points"] = [];
+ const points: [number, number][] = [];
commands.forEach((command) => {
- const currPoints = command
+ const currPoints: [number, number][] = command
.substring(1)
.trim()
.split(" ")
@@ -134,7 +151,12 @@ export const createArrowSkeletonFromSVG = (
arrow.endY = arrow.endY - offset;
}
- arrow.strokeColor = arrowNode.getAttribute("stroke");
+ const strokeAttr = arrowNode.getAttribute("stroke");
+ const strokeColor =
+ (strokeAttr && strokeAttr !== "none" ? strokeAttr : "") ||
+ getComputedStyle(arrowNode as Element).stroke ||
+ "";
+ arrow.strokeColor = strokeColor ? cleanCSSValue(strokeColor) : null;
arrow.strokeWidth = Number(arrowNode.getAttribute("stroke-width"));
arrow.type = "arrow";
arrow.strokeStyle = opts?.strokeStyle || "solid";
@@ -153,8 +175,8 @@ export const createArrowSkeletion = (
label?: Arrow["label"];
strokeColor?: Arrow["strokeColor"];
strokeStyle?: Arrow["strokeStyle"];
- startArrowhead?: Arrow["startArrowhead"];
- endArrowhead?: Arrow["endArrowhead"];
+ startArrowhead?: SupportedArrowhead;
+ endArrowhead?: SupportedArrowhead;
start?: Arrow["start"];
end?: Arrow["end"];
points?: Arrow["points"];
@@ -180,6 +202,7 @@ export const createTextSkeleton = (
width?: number;
height?: number;
fontSize?: number;
+ color?: string;
groupId?: string;
metadata?: { [key: string]: any };
}
@@ -194,6 +217,7 @@ export const createTextSkeleton = (
fontSize: opts?.fontSize || DEFAULT_FONT_SIZE,
id: opts?.id,
+ color: opts?.color,
groupId: opts?.groupId,
metadata: opts?.metadata,
};
@@ -223,6 +247,7 @@ export const createTextSkeletonFromSVG = (
node.y = y;
const fontSize = parseInt(getComputedStyle(textNode).fontSize);
node.fontSize = fontSize;
+ node.color = resolveElementTextColor(textNode);
return node;
};
@@ -233,6 +258,7 @@ export const createContainerSkeletonFromSVG = (
id?: string;
label?: {
text: string;
+ textAlign?: ExcalidrawTextElement["textAlign"];
verticalAlign?: ExcalidrawTextElement["verticalAlign"];
};
subtype?: Container["subtype"];
@@ -250,6 +276,7 @@ export const createContainerSkeletonFromSVG = (
container.label = {
text: entityCodesToText(label.text),
fontSize: 16,
+ textAlign: label?.textAlign,
verticalAlign: label?.verticalAlign,
};
}
@@ -263,7 +290,7 @@ export const createContainerSkeletonFromSVG = (
case "highlight":
const bgColor = node.getAttribute("fill");
if (bgColor) {
- container.bgColor = bgColor;
+ container.bgColor = cleanCSSValue(bgColor);
}
break;
case "note":
@@ -294,7 +321,8 @@ export const createLineSkeletonFromSVG = (
}
// Make sure lines don't overlap with the nodes, in mermaid it overlaps but isn't visible as its pushed back and containers are non transparent
line.endY = endY;
- line.strokeColor = lineNode.getAttribute("stroke");
+ const strokeColor = lineNode.getAttribute("stroke");
+ line.strokeColor = strokeColor ? cleanCSSValue(strokeColor) : null;
line.strokeWidth = Number(lineNode.getAttribute("stroke-width"));
line.type = "line";
return line;
diff --git a/src/graphToExcalidraw.ts b/src/graphToExcalidraw.ts
index 9b26a380..83b1606d 100644
--- a/src/graphToExcalidraw.ts
+++ b/src/graphToExcalidraw.ts
@@ -7,34 +7,74 @@ import { Sequence } from "./parser/sequence.js";
import { Flowchart } from "./parser/flowchart.js";
import { Class } from "./parser/class.js";
import { classToExcalidrawSkeletonConvertor } from "./converter/types/class.js";
+import { ERD } from "./parser/er.js";
+import { erToExcalidrawSkeletonConvertor } from "./converter/types/er.js";
+import type { LocalPoint } from "@excalidraw/excalidraw/math/types";
+import { dedupeConsecutivePoints } from "./utils.js";
+
+const normalizeLinearElementPoints = (
+ result: MermaidToExcalidrawResult
+): MermaidToExcalidrawResult => {
+ return {
+ ...result,
+ elements: result.elements.map((element) => {
+ if (!("points" in element) || !Array.isArray(element.points)) {
+ return element;
+ }
+
+ const points = element.points as readonly LocalPoint[];
+ if (points.length < 2) {
+ return element;
+ }
+
+ const dedupedPoints = dedupeConsecutivePoints(points);
+ if (dedupedPoints.length === points.length) {
+ return element;
+ }
+
+ return {
+ ...element,
+ points: dedupedPoints,
+ };
+ }),
+ };
+};
export const graphToExcalidraw = (
- graph: Flowchart | GraphImage | Sequence | Class,
+ graph: Flowchart | GraphImage | Sequence | Class | ERD,
options: ExcalidrawConfig = {}
): MermaidToExcalidrawResult => {
- switch (graph.type) {
- case "graphImage": {
- return GraphImageConverter.convert(graph, options);
- }
+ const result = (() => {
+ switch (graph.type) {
+ case "graphImage": {
+ return GraphImageConverter.convert(graph, options);
+ }
- case "flowchart": {
- return FlowchartToExcalidrawSkeletonConverter.convert(graph, options);
- }
+ case "flowchart": {
+ return FlowchartToExcalidrawSkeletonConverter.convert(graph, options);
+ }
- case "sequence": {
- return SequenceToExcalidrawSkeletonConvertor.convert(graph, options);
- }
+ case "sequence": {
+ return SequenceToExcalidrawSkeletonConvertor.convert(graph, options);
+ }
- case "class": {
- return classToExcalidrawSkeletonConvertor.convert(graph, options);
- }
+ case "class": {
+ return classToExcalidrawSkeletonConvertor.convert(graph, options);
+ }
+
+ case "erd": {
+ return erToExcalidrawSkeletonConvertor.convert(graph, options);
+ }
- default: {
- throw new Error(
- `graphToExcalidraw: unknown graph type "${
- (graph as any).type
- }, only flowcharts are supported!"`
- );
+ default: {
+ throw new Error(
+ `graphToExcalidraw: unknown graph type "${
+ (graph as any).type
+ }, only flowcharts are supported!"`
+ );
+ }
}
- }
+ })();
+
+ return normalizeLinearElementPoints(result);
};
diff --git a/src/index.ts b/src/index.ts
index d2d858ce..cfdbb72f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,6 @@
import { DEFAULT_FONT_SIZE } from "./constants.js";
import { graphToExcalidraw } from "./graphToExcalidraw.js";
import { parseMermaid } from "./parseMermaid.js";
-import { validateMermaid } from "./validateMermaid.js";
export interface MermaidConfig {
/**
@@ -41,7 +40,7 @@ export interface ExcalidrawConfig {
const parseMermaidToExcalidraw = async (
definition: string,
- config?: MermaidConfig
+ config?: MermaidConfig,
) => {
const mermaidConfig = config || {};
const fontSize =
@@ -50,8 +49,6 @@ const parseMermaidToExcalidraw = async (
...mermaidConfig,
themeVariables: {
...mermaidConfig.themeVariables,
- // Multiplying by 1.25 to increase the font size by 25% and render correctly in Excalidraw
- fontSize: `${fontSize * 1.25}px`,
},
});
// Only font size supported for excalidraw elements
@@ -61,4 +58,4 @@ const parseMermaidToExcalidraw = async (
return excalidrawElements;
};
-export { parseMermaidToExcalidraw, validateMermaid };
+export { parseMermaidToExcalidraw };
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 71c24577..672d21d7 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -1,5 +1,5 @@
-import { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/types/data/transform.js";
-import { BinaryFiles } from "@excalidraw/excalidraw/types/types.js";
+import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/element/transform";
+import type { BinaryFiles } from "@excalidraw/excalidraw/types";
export enum VERTEX_TYPE {
ROUND = "round",
@@ -7,6 +7,7 @@ export enum VERTEX_TYPE {
DOUBLECIRCLE = "doublecircle",
CIRCLE = "circle",
DIAMOND = "diamond",
+ CYLINDER = "cylinder",
}
export enum LABEL_STYLE_PROPERTY {
COLOR = "color",
@@ -17,6 +18,15 @@ export enum CONTAINER_STYLE_PROPERTY {
STROKE_WIDTH = "stroke-width",
STROKE_DASHARRAY = "stroke-dasharray",
}
+
+export type ContainerStyle = {
+ [key in CONTAINER_STYLE_PROPERTY]?: string;
+};
+
+export type LabelStyle = {
+ [key in LABEL_STYLE_PROPERTY]?: string;
+};
+
export interface Vertex {
id: string;
type: VERTEX_TYPE;
@@ -27,8 +37,8 @@ export interface Vertex {
width: number;
height: number;
link?: string;
- containerStyle: { [key in CONTAINER_STYLE_PROPERTY]?: string };
- labelStyle: { [key in LABEL_STYLE_PROPERTY]?: string };
+ containerStyle: ContainerStyle;
+ labelStyle: LabelStyle;
}
export interface SubGraph {
@@ -40,6 +50,8 @@ export interface SubGraph {
y: number;
width: number;
height: number;
+ containerStyle: ContainerStyle;
+ labelStyle: LabelStyle;
}
export interface Position {
diff --git a/src/mermaidExecutionQueue.ts b/src/mermaidExecutionQueue.ts
new file mode 100644
index 00000000..7d65ad3c
--- /dev/null
+++ b/src/mermaidExecutionQueue.ts
@@ -0,0 +1,27 @@
+// Module-scoped tail promise shared by this bundle's Mermaid callers.
+let mermaidTaskQueue: Promise = Promise.resolve();
+
+/**
+ * Serializes Mermaid work behind a single promise tail.
+ *
+ * This exists because Mermaid is effectively a singleton in-process:
+ * `mermaid.initialize()` mutates shared config, `getDiagramFromText()` and
+ * `render()` use shared internal state, and `render()` also inserts temporary
+ * DOM nodes while it works. If those operations overlap, callers can race on
+ * config, parser state, and transient DOM.
+ *
+ * The returned promise preserves the caller's real result or error. The
+ * internal queue tail is normalized back to `Promise` so a failed task
+ * does not poison the queue or leave the queue state holding an unhandled
+ * rejection.
+ */
+export const runMermaidTaskSequentially = (task: () => Promise) => {
+ const run = mermaidTaskQueue.then(task, task);
+
+ mermaidTaskQueue = run.then(
+ () => undefined,
+ () => undefined
+ );
+
+ return run;
+};
diff --git a/src/parseMermaid.ts b/src/parseMermaid.ts
index 156fa3c1..42acfe37 100644
--- a/src/parseMermaid.ts
+++ b/src/parseMermaid.ts
@@ -1,10 +1,26 @@
-import mermaid, { MermaidConfig } from "mermaid";
+import mermaid from "mermaid";
+import type { MermaidConfig } from "mermaid";
+import type { Diagram } from "mermaid/dist/Diagram.js";
+import type { FlowDB } from "mermaid/dist/diagrams/flowchart/flowDb.js";
+import type { ErDB } from "mermaid/dist/diagrams/er/erDb.js";
+
import { GraphImage } from "./interfaces.js";
import { MERMAID_CONFIG } from "./constants.js";
import { encodeEntities } from "./utils.js";
import { Flowchart, parseMermaidFlowChartDiagram } from "./parser/flowchart.js";
import { Sequence, parseMermaidSequenceDiagram } from "./parser/sequence.js";
import { Class, parseMermaidClassDiagram } from "./parser/class.js";
+import { ERD, parseMermaidERDiagram } from "./parser/er.js";
+import { runMermaidTaskSequentially } from "./mermaidExecutionQueue.js";
+
+// Track initialization state to avoid redundant mermaid.initialize() calls
+// which can cause performance issues during streaming
+let lastConfigHash: string | null = null;
+let renderCounter = 0;
+
+const hashConfig = (config: MermaidConfig): string => {
+ return JSON.stringify(config);
+};
// Fallback to Svg
const convertSvgToGraphImage = (svgContainer: HTMLDivElement) => {
@@ -45,48 +61,97 @@ const convertSvgToGraphImage = (svgContainer: HTMLDivElement) => {
export const parseMermaid = async (
definition: string,
config: MermaidConfig = MERMAID_CONFIG
-): Promise => {
- mermaid.initialize({ ...MERMAID_CONFIG, ...config });
- // Parse the diagram
- const diagram = await mermaid.mermaidAPI.getDiagramFromText(
- encodeEntities(definition)
- );
-
- // Render the SVG diagram
- const { svg } = await mermaid.render("mermaid-to-excalidraw", definition);
-
- // Append Svg to DOM
- const svgContainer = document.createElement("div");
- svgContainer.setAttribute(
- "style",
- `opacity: 0; position: relative; z-index: -1;`
- );
- svgContainer.innerHTML = svg;
- svgContainer.id = "mermaid-diagram";
- document.body.appendChild(svgContainer);
- let data;
- switch (diagram.type) {
- case "flowchart-v2": {
- data = parseMermaidFlowChartDiagram(diagram, svgContainer);
- break;
- }
+): Promise => {
+ return runMermaidTaskSequentially(async () => {
+ const resolvedFontSize =
+ config.themeVariables?.fontSize ?? MERMAID_CONFIG.themeVariables.fontSize;
- case "sequence": {
- data = parseMermaidSequenceDiagram(diagram, svgContainer);
+ // Only re-initialize mermaid if config has changed (performance optimization)
+ const mergedConfig = {
+ ...MERMAID_CONFIG,
+ ...config,
+ fontSize: resolvedFontSize,
+ themeVariables: {
+ ...MERMAID_CONFIG.themeVariables,
+ ...config.themeVariables,
+ fontSize: resolvedFontSize,
+ },
+ };
+ const configHash = hashConfig(mergedConfig);
- break;
+ if (configHash !== lastConfigHash) {
+ mermaid.initialize(mergedConfig);
+ lastConfigHash = configHash;
}
- case "classDiagram": {
- data = parseMermaidClassDiagram(diagram, svgContainer);
- break;
- }
- // fallback to image if diagram type not-supported
- default: {
- data = convertSvgToGraphImage(svgContainer);
- }
- }
- svgContainer.remove();
+ // Parse the diagram definition
+ // Note: mermaidAPI.getDiagramFromText is deprecated but there's no public
+ // alternative that provides access to diagram.db (needed for extracting nodes/edges).
+ // See: https://github.com/mermaid-js/mermaid/issues/XXX
+ const diagram: Diagram = await mermaid.mermaidAPI.getDiagramFromText(
+ encodeEntities(definition)
+ );
- return data;
+ // Use unique render IDs to avoid conflicts when streaming (performance optimization)
+ const renderId = `mermaid-to-excalidraw-${renderCounter++}`;
+
+ // Use an off-screen container so Mermaid's temporary DOM insertions don't shift layout.
+ const svgContainer = document.createElement("div");
+ svgContainer.setAttribute(
+ "style",
+ `opacity: 0; position: fixed; z-index: -1; left: -99999px; top: -99999px;`
+ );
+
+ const containerId = `${renderId}-container`;
+ svgContainer.id = containerId;
+ // Clean up any previous container with the same ID (shouldn't exist due to unique IDs, but defensive)
+ document.getElementById(containerId)?.remove();
+ document.body.appendChild(svgContainer);
+
+ try {
+ // Render the SVG diagram to be able to query DOM elements
+ const { svg } = await mermaid.render(renderId, definition, svgContainer);
+
+ // Append SVG to DOM temporarily to allow querying element dimensions/positions
+ svgContainer.innerHTML = svg;
+
+ let data: Flowchart | GraphImage | Sequence | Class | ERD;
+
+ try {
+ switch (diagram.type) {
+ case "flowchart-v2":
+ case "graph": {
+ data = parseMermaidFlowChartDiagram(
+ diagram.db as FlowDB,
+ svgContainer
+ );
+ break;
+ }
+ case "sequence": {
+ data = parseMermaidSequenceDiagram(diagram, svgContainer);
+ break;
+ }
+ case "class":
+ case "classDiagram": {
+ data = parseMermaidClassDiagram(diagram, svgContainer);
+ break;
+ }
+ case "er": {
+ data = parseMermaidERDiagram(diagram.db as ErDB, svgContainer);
+ break;
+ }
+ default: {
+ data = convertSvgToGraphImage(svgContainer);
+ }
+ }
+ } catch (error) {
+ console.error("Error processing Mermaid diagram:", error);
+ data = convertSvgToGraphImage(svgContainer);
+ }
+
+ return data;
+ } finally {
+ svgContainer.remove();
+ }
+ });
};
diff --git a/src/parser/class.ts b/src/parser/class.ts
index 82185da4..01e64ece 100644
--- a/src/parser/class.ts
+++ b/src/parser/class.ts
@@ -1,6 +1,10 @@
import { nanoid } from "nanoid";
-import { computeEdgePositions, getTransformAttr } from "../utils.js";
+import {
+ computeEdgePositions,
+ entityCodesToText,
+ getTransformAttr,
+} from "../utils.js";
import {
Arrow,
Container,
@@ -12,6 +16,27 @@ import {
createLineSkeletonFromSVG,
createTextSkeleton,
} from "../elementSkeleton.js";
+import {
+ cleanCSSValue,
+ isValidCSSColor,
+ parseCSSDeclarations,
+ resolveElementTextColor,
+} from "./cssUtils.js";
+
+const parseStyleStrings = (styles?: string[]) => {
+ const styleObj: Record = {};
+ if (!styles) {
+ return styleObj;
+ }
+ styles.forEach((style) => {
+ parseCSSDeclarations(style).forEach(({ property, value }) => {
+ if (property && value) {
+ styleObj[property] = cleanCSSValue(value);
+ }
+ });
+ });
+ return styleObj;
+};
import type { Diagram } from "mermaid/dist/Diagram.js";
import type {
@@ -20,7 +45,7 @@ import type {
ClassRelation,
NamespaceNode,
} from "mermaid/dist/diagrams/class/classTypes.js";
-import type { ExcalidrawLinearElement } from "@excalidraw/excalidraw/types/element/types.js";
+import type { ExcalidrawLinearElement } from "@excalidraw/excalidraw/element/types";
// Taken from mermaidParser.relationType
const RELATION_TYPE = {
@@ -92,9 +117,52 @@ const getArrowhead = (type: RELATION_TYPE_VALUES) => {
return arrowhead;
};
+const accumulateTranslation = (node: Element, stopAt?: Element | null) => {
+ let tx = 0;
+ let ty = 0;
+ let current: Element | null = node;
+
+ while (current && current !== stopAt) {
+ const { transformX, transformY } = getTransformAttr(current);
+ tx += transformX;
+ ty += transformY;
+ current = current.parentElement;
+ }
+
+ return { tx, ty };
+};
+
+type ClassTextSection = "header" | "members" | "methods" | "other";
+
+const getClassTextSection = (
+ textNode: Element,
+ classNode: Element
+): ClassTextSection => {
+ let current: Element | null = textNode;
+
+ while (current && current !== classNode) {
+ if (current.classList.contains("annotation-group")) {
+ return "header";
+ }
+ if (current.classList.contains("label-group")) {
+ return "header";
+ }
+ if (current.classList.contains("members-group")) {
+ return "members";
+ }
+ if (current.classList.contains("methods-group")) {
+ return "methods";
+ }
+ current = current.parentElement;
+ }
+
+ return "other";
+};
+
const parseClasses = (
classes: { [key: string]: ClassNode },
- containerEl: Element
+ containerEl: Element,
+ lookUpDomId?: (id: string) => string | undefined
) => {
const nodes: Container[] = [];
const lines: Line[] = [];
@@ -103,32 +171,167 @@ const parseClasses = (
Object.values(classes).forEach((classNode) => {
const { domId, id: classId } = classNode;
const groupId = nanoid();
- const domNode = containerEl.querySelector(`[data-id=${classId}]`);
+
+ const classStyles = parseStyleStrings(
+ // @ts-ignore
+ (classNode as any).styles || (classNode as any).cssStyles
+ );
+
+ // Mermaid v11 generates class groups with ids like "classId--"
+ // but the domId stored on the class might not exactly match. Try a
+ // few fallbacks so we can find the correct group.
+ let lookedUpId: string | undefined;
+ try {
+ lookedUpId = lookUpDomId ? lookUpDomId(classId) : undefined;
+ } catch {
+ lookedUpId = undefined;
+ }
+
+ const findByPrefix = (id: string) => {
+ const regex = new RegExp(`^classId-${id}(?:-|$)`);
+ const all = Array.from(
+ containerEl.querySelectorAll("[id]")
+ ).filter((el) => regex.test(el.id));
+ return all[0];
+ };
+
+ const domNode =
+ (lookedUpId &&
+ containerEl.querySelector(`#${lookedUpId}`)) ||
+ containerEl.querySelector(`#${domId}`) ||
+ containerEl.querySelector(`[data-id='${classId}']`) ||
+ findByPrefix(classId);
+
if (!domNode) {
throw Error(`DOM Node with id ${domId} not found`);
}
- const { transformX, transformY } = getTransformAttr(domNode);
- const container = createContainerSkeletonFromSVG(
- domNode.firstChild as SVGRectElement,
- "rectangle",
- { id: classId, groupId }
+ // Prefer the explicit rect if present, otherwise fall back to the group bbox
+ const containerSource =
+ (domNode.querySelector("rect") as SVGGraphicsElement | null) || domNode;
+
+ const containerBBox = containerSource.getBBox();
+ const { tx: containerTx, ty: containerTy } = accumulateTranslation(
+ containerSource,
+ containerEl
);
- container.x += transformX;
- container.y += transformY;
- container.metadata = { classId };
+
+ const container: Container = {
+ type: "rectangle",
+ id: classId,
+ groupId,
+ x: containerBBox.x + containerTx,
+ y: containerBBox.y + containerTy,
+ width: containerBBox.width,
+ height: containerBBox.height,
+ metadata: { classId },
+ };
+
+ // Apply styles from rendered shape (fill/stroke/dash)
+ const fill = containerSource.getAttribute("fill");
+ const stroke = containerSource.getAttribute("stroke");
+ const strokeWidth = containerSource.getAttribute("stroke-width");
+ const dashArray = containerSource.getAttribute("stroke-dasharray");
+
+ const computed = getComputedStyle(containerSource as Element);
+ // Only fall back to computed styles when an explicit attribute exists; otherwise leave undefined for defaults
+ const resolvedFill = cleanCSSValue(
+ fill || classStyles.fill || (fill ? computed.fill : "")
+ );
+ const resolvedStroke = cleanCSSValue(
+ stroke || classStyles.stroke || (stroke ? computed.stroke : "")
+ );
+ const resolvedStrokeWidth =
+ strokeWidth ||
+ classStyles["stroke-width"] ||
+ (strokeWidth ? computed.strokeWidth : "");
+ const resolvedDash =
+ dashArray ||
+ classStyles["stroke-dasharray"] ||
+ (dashArray
+ ? computed.strokeDasharray === "none"
+ ? ""
+ : computed.strokeDasharray
+ : "");
+
+ const isMeaningfulColor = (value: string) => {
+ if (!value) {
+ return false;
+ }
+ if (!isValidCSSColor(value)) {
+ return false;
+ }
+ const v = value.toLowerCase();
+ return !(
+ v === "none" ||
+ v === "transparent" ||
+ v === "rgba(0, 0, 0, 0)" ||
+ v === "black" ||
+ v === "#000" ||
+ v === "#000000" ||
+ v === "rgb(0, 0, 0)" ||
+ v === "rgba(0, 0, 0, 1)"
+ );
+ };
+
+ if (isMeaningfulColor(resolvedFill)) {
+ container.bgColor = resolvedFill;
+ } else {
+ container.bgColor = undefined;
+ }
+ if (isMeaningfulColor(resolvedStroke)) {
+ container.strokeColor = resolvedStroke;
+ } else {
+ container.strokeColor = undefined;
+ }
+ if (resolvedStrokeWidth) {
+ container.strokeWidth = Number(resolvedStrokeWidth);
+ } else {
+ container.strokeWidth = undefined;
+ }
+ if (resolvedDash && resolvedDash.trim().length > 0) {
+ container.strokeStyle = "dashed";
+ } else {
+ container.strokeStyle = undefined;
+ }
+
nodes.push(container);
- const lineNodes = Array.from(
- domNode.querySelectorAll(".divider")
- ) as SVGLineElement[];
+ // Divider lines inside the class container (members/methods split)
+ const lineNodes = [
+ ...Array.from(domNode.querySelectorAll("line")),
+ ...Array.from(domNode.querySelectorAll("g.divider path")),
+ ] as SVGGraphicsElement[];
lineNodes.forEach((lineNode) => {
- const startX = Number(lineNode.getAttribute("x1"));
- const startY = Number(lineNode.getAttribute("y1"));
- const endX = Number(lineNode.getAttribute("x2"));
- const endY = Number(lineNode.getAttribute("y2"));
+ const { tx, ty } = accumulateTranslation(lineNode, containerEl);
+
+ let startX: number;
+ let startY: number;
+ let endX: number;
+ let endY: number;
+
+ if (lineNode.tagName.toLowerCase() === "line") {
+ startX = Number(lineNode.getAttribute("x1")) + tx;
+ startY = Number(lineNode.getAttribute("y1")) + ty;
+ endX = Number(lineNode.getAttribute("x2")) + tx;
+ endY = Number(lineNode.getAttribute("y2")) + ty;
+ } else {
+ const bbox = lineNode.getBBox();
+ startX = bbox.x + tx;
+ endX = bbox.x + bbox.width + tx;
+ const centerY = bbox.y + bbox.height / 2 + ty;
+ startY = centerY;
+ endY = centerY;
+ }
+
+ // Skip zero-length lines (happens when class has no members)
+ if (startX === endX && startY === endY) {
+ return;
+ }
+
const line = createLineSkeletonFromSVG(
+ // @ts-ignore
lineNode,
startX,
startY,
@@ -139,47 +342,150 @@ const parseClasses = (
id: nanoid(),
}
);
- line.startX += transformX;
- line.startY += transformY;
- line.endX += transformX;
- line.endY += transformY;
+ // Only inherit styling when explicitly set; otherwise keep defaults
+ if (container.strokeColor) {
+ line.strokeColor = container.strokeColor;
+ } else {
+ line.strokeColor = undefined;
+ }
+ if (container.strokeWidth !== undefined) {
+ line.strokeWidth = container.strokeWidth;
+ } else {
+ line.strokeWidth = undefined;
+ }
+ if (container.strokeStyle) {
+ line.strokeStyle = container.strokeStyle;
+ } else {
+ line.strokeStyle = undefined;
+ }
line.metadata = { classId };
+
lines.push(line);
});
- const labelNodes = domNode.querySelector(".label")?.children;
-
- if (!labelNodes) {
- throw "label nodes not found";
- }
-
- Array.from(labelNodes).forEach((node) => {
- const label = node.textContent;
- if (!label) {
+ // Parse text elements (class titles, members, methods)
+ const textElements = Array.from(
+ domNode.querySelectorAll("text, foreignObject")
+ ) as SVGGraphicsElement[];
+
+ const parsedTextElements: Array<{
+ section: ClassTextSection;
+ text: string;
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ fontSize: number;
+ color?: string;
+ }> = [];
+
+ textElements.forEach((textNode) => {
+ const isForeignObject =
+ textNode.tagName.toLowerCase() === "foreignobject";
+
+ const tspans = !isForeignObject
+ ? Array.from(textNode.querySelectorAll("tspan"))
+ : [];
+
+ const rawText = tspans.length
+ ? tspans
+ .map((span) => span.textContent?.trim())
+ .filter(Boolean)
+ .join("\n")
+ : textNode.textContent?.trim() || "";
+
+ if (!rawText) {
return;
}
- const id = nanoid();
- const { transformX: textTransformX, transformY: textTransformY } =
- getTransformAttr(node);
- const boundingBox = (node as SVGForeignObjectElement).getBBox();
- const offsetY = 10;
+ const boundingBox = textNode.getBBox();
+ const { ty } = accumulateTranslation(textNode, containerEl);
+ let fontSize = parseFloat(getComputedStyle(textNode).fontSize || "");
- const textElement = createTextSkeleton(
- transformX + textTransformX,
- transformY + textTransformY + offsetY,
- label,
- {
- width: boundingBox.width,
- height: boundingBox.height,
- id,
- groupId,
- metadata: { classId },
+ if (isForeignObject && (!Number.isFinite(fontSize) || !fontSize)) {
+ const inner = textNode.querySelector("div, span, p");
+ if (inner) {
+ fontSize = parseFloat(getComputedStyle(inner).fontSize || "");
}
+ }
+
+ if (!Number.isFinite(fontSize) || fontSize <= 0) {
+ fontSize = Math.max(12, boundingBox.height * 0.6);
+ }
+
+ // Slightly reduce font size to better fit the original box dimensions
+ fontSize = fontSize * 0.9;
+
+ const resolvedTextColor = resolveElementTextColor(
+ textNode,
+ classStyles.color
);
- text.push(textElement);
+ parsedTextElements.push({
+ section: getClassTextSection(textNode, domNode),
+ text: entityCodesToText(rawText),
+ x: boundingBox.x,
+ y: boundingBox.y + ty,
+ width:
+ container && container.width
+ ? Math.max(container.width - 8, boundingBox.width)
+ : boundingBox.width,
+ height: boundingBox.height,
+ fontSize,
+ color: resolvedTextColor,
+ });
});
+
+ const headerTextElements = parsedTextElements
+ .filter((element) => element.section === "header")
+ .sort((a, b) => a.y - b.y || a.x - b.x);
+
+ if (!container.label) {
+ const fallbackHeaderTextElements =
+ headerTextElements.length === 0 && parsedTextElements.length === 1
+ ? parsedTextElements
+ : headerTextElements;
+
+ if (fallbackHeaderTextElements.length > 0) {
+ container.label = {
+ text: fallbackHeaderTextElements
+ .map((element) => element.text)
+ .join("\n"),
+ fontSize: Math.max(
+ ...fallbackHeaderTextElements.map((element) => element.fontSize)
+ ),
+ color: fallbackHeaderTextElements.find((element) => element.color)
+ ?.color,
+ verticalAlign: "top",
+ };
+ }
+ }
+
+ parsedTextElements
+ .filter((element) => {
+ if (headerTextElements.length > 0) {
+ return element.section !== "header";
+ }
+ return !(container.label && parsedTextElements.length === 1);
+ })
+ .forEach((element) => {
+ const textElement = createTextSkeleton(
+ (container?.x || 0) + 4,
+ element.y,
+ element.text,
+ {
+ width: element.width,
+ height: element.height,
+ fontSize: element.fontSize,
+ color: element.color,
+ id: nanoid(),
+ groupId,
+ metadata: { classId },
+ }
+ );
+
+ text.push(textElement);
+ });
});
return { nodes, lines, text };
};
@@ -235,16 +541,24 @@ const parseRelations = (
) => {
const edges = containerEl.querySelector(".edgePaths")?.children;
- if (!edges) {
- throw new Error("No Edges found!");
+ // If there are no relations, return empty arrays
+ if (!edges || relations.length === 0) {
+ return { arrows: [], text: [] };
}
const arrows: Arrow[] = [];
const text: Text[] = [];
relations.forEach((relationNode, index) => {
const { id1, id2, relation } = relationNode;
- const node1 = classNodes.find((node) => node.id === id1)!;
- const node2 = classNodes.find((node) => node.id === id2)!;
+ const node1 = classNodes.find((node) => node.id === id1);
+ const node2 = classNodes.find((node) => node.id === id2);
+
+ if (!node1) {
+ throw new Error(`parseRelations: Cannot find node with id ${id1}`);
+ }
+ if (!node2) {
+ throw new Error(`parseRelations: Cannot find node with id ${id2}`);
+ }
const strokeStyle = getStrokeStyle(relation.lineType);
const startArrowhead = getArrowhead(relation.type1);
@@ -416,27 +730,45 @@ export const parseMermaidClassDiagram = (
diagram: Diagram,
containerEl: Element
): Class => {
- diagram.parse();
+ // In Mermaid v11, use diagram.db instead of parser.yy
+ //@ts-ignore - ClassDB type not properly exported
+ const db = diagram.db;
+
//@ts-ignore
- const mermaidParser = diagram.parser.yy;
- const direction = mermaidParser.getDirection();
+ const direction: "LR" | "RL" | "TB" | "BT" = db.getDirection?.() || "TB";
const nodes: Array = [];
const lines: Array = [];
const text: Array = [];
const classNodes: Array = [];
- const namespaces: NamespaceNode[] = mermaidParser.getNamespaces();
+ //@ts-ignore
+ const namespaces: NamespaceNode[] = db.getNamespaces?.() || [];
+
+ //@ts-ignore
+ const classesData = db.getClasses?.() || {};
+
+ // Convert Map to object if necessary
+ const classes: { [key: string]: ClassNode } =
+ classesData instanceof Map ? Object.fromEntries(classesData) : classesData;
+
+ if (classes && Object.keys(classes).length) {
+ const lookUpDomId =
+ //@ts-ignore
+ typeof db.lookUpDomId === "function"
+ ? //@ts-ignore
+ db.lookUpDomId.bind(db)
+ : undefined;
- const classes = mermaidParser.getClasses();
- if (Object.keys(classes).length) {
- const classData = parseClasses(classes, containerEl);
+ const classData = parseClasses(classes, containerEl, lookUpDomId);
nodes.push(classData.nodes);
lines.push(...classData.lines);
text.push(...classData.text);
classNodes.push(...classData.nodes);
}
- const relations = mermaidParser.getRelations();
+
+ //@ts-ignore
+ const relations = db.getRelations?.() || [];
const { arrows, text: relationTitles } = parseRelations(
relations,
classNodes,
@@ -444,12 +776,14 @@ export const parseMermaidClassDiagram = (
direction
);
- const { notes, connectors } = parseNotes(
- mermaidParser.getNotes(),
+ //@ts-ignore
+ const notes = db.getNotes?.() || [];
+ const { notes: noteContainers, connectors } = parseNotes(
+ notes,
containerEl,
classNodes
);
- nodes.push(notes);
+ nodes.push(noteContainers);
arrows.push(...connectors);
text.push(...relationTitles);
diff --git a/src/parser/cssUtils.ts b/src/parser/cssUtils.ts
new file mode 100644
index 00000000..b5d33e45
--- /dev/null
+++ b/src/parser/cssUtils.ts
@@ -0,0 +1,235 @@
+/**
+ * Cleans CSS property values by removing !important and trimming whitespace
+ * This ensures that colors and other CSS values are valid and don't contain
+ * CSS priority declarations that could cause issues in rendering.
+ *
+ * @param value - The CSS value to clean
+ * @returns The cleaned CSS value without !important
+ *
+ * @example
+ * cleanCSSValue("red !important") // => "red"
+ * cleanCSSValue(" blue ") // => "blue"
+ * cleanCSSValue("#ff0000 !IMPORTANT") // => "#ff0000"
+ */
+export const cleanCSSValue = (value: string): string => {
+ return value.replace(/\s*!important\s*$/i, "").trim();
+};
+
+const looksLikeDeclarationStart = (input: string, index: number) => {
+ let cursor = index;
+ while (cursor < input.length && /\s/.test(input[cursor])) {
+ cursor += 1;
+ }
+
+ const propertyStart = cursor;
+ while (cursor < input.length && /[a-z-]/i.test(input[cursor])) {
+ cursor += 1;
+ }
+
+ if (cursor === propertyStart) {
+ return false;
+ }
+
+ while (cursor < input.length && /\s/.test(input[cursor])) {
+ cursor += 1;
+ }
+
+ return input[cursor] === ":";
+};
+
+export const parseCSSDeclarations = (
+ input: string
+): Array<{ property: string; value: string }> => {
+ const declarations: Array<{ property: string; value: string }> = [];
+ let cursor = 0;
+
+ while (cursor < input.length) {
+ while (cursor < input.length && /[\s;,]/.test(input[cursor])) {
+ cursor += 1;
+ }
+
+ if (cursor >= input.length) {
+ break;
+ }
+
+ const propertyStart = cursor;
+ while (cursor < input.length && input[cursor] !== ":") {
+ if (input[cursor] === ";" || input[cursor] === ",") {
+ break;
+ }
+ cursor += 1;
+ }
+
+ if (cursor >= input.length || input[cursor] !== ":") {
+ break;
+ }
+
+ const property = input
+ .substring(propertyStart, cursor)
+ .trim()
+ .toLowerCase();
+ cursor += 1;
+
+ const valueStart = cursor;
+ let parenthesesDepth = 0;
+ let quote: '"' | "'" | null = null;
+
+ while (cursor < input.length) {
+ const currentChar = input[cursor];
+
+ if (quote) {
+ if (currentChar === quote && input[cursor - 1] !== "\\") {
+ quote = null;
+ }
+ cursor += 1;
+ continue;
+ }
+
+ if (currentChar === '"' || currentChar === "'") {
+ quote = currentChar;
+ cursor += 1;
+ continue;
+ }
+
+ if (currentChar === "(") {
+ parenthesesDepth += 1;
+ cursor += 1;
+ continue;
+ }
+
+ if (currentChar === ")") {
+ parenthesesDepth = Math.max(0, parenthesesDepth - 1);
+ cursor += 1;
+ continue;
+ }
+
+ if (parenthesesDepth === 0) {
+ if (currentChar === ";" || currentChar === ",") {
+ break;
+ }
+
+ if (
+ /\s/.test(currentChar) &&
+ looksLikeDeclarationStart(input, cursor)
+ ) {
+ break;
+ }
+ }
+
+ cursor += 1;
+ }
+
+ const value = cleanCSSValue(input.substring(valueStart, cursor));
+ if (property && value) {
+ declarations.push({ property, value });
+ }
+
+ if (
+ cursor < input.length &&
+ (input[cursor] === ";" || input[cursor] === ",")
+ ) {
+ cursor += 1;
+ }
+ }
+
+ return declarations;
+};
+
+export const isValidCSSColor = (value: string): boolean => {
+ const cleanedValue = cleanCSSValue(value);
+ if (!cleanedValue) {
+ return false;
+ }
+
+ if (typeof CSS !== "undefined" && typeof CSS.supports === "function") {
+ return CSS.supports("color", cleanedValue);
+ }
+
+ if (typeof document !== "undefined") {
+ const element = document.createElement("div");
+ element.style.color = "";
+ element.style.color = cleanedValue;
+ return element.style.color !== "";
+ }
+
+ return false;
+};
+
+const getStyleDeclarationValue = (element: Element, property: string) => {
+ const styleText = element.getAttribute("style");
+ if (!styleText) {
+ return "";
+ }
+
+ return (
+ parseCSSDeclarations(styleText).find(
+ (declaration) => declaration.property === property
+ )?.value || ""
+ );
+};
+
+const resolveFirstValidCSSColor = (
+ ...values: Array
+): string | undefined => {
+ for (const rawValue of values) {
+ const value = cleanCSSValue(rawValue || "");
+ if (isValidCSSColor(value)) {
+ return value;
+ }
+ }
+
+ return undefined;
+};
+
+export const resolveElementTextColor = (
+ node: Element,
+ fallbackColor?: string
+): string | undefined => {
+ const textNode =
+ node.querySelector("text, foreignObject, div, span, p") ||
+ (node as HTMLElement);
+
+ const inlineFill = resolveFirstValidCSSColor(
+ textNode.getAttribute?.("fill"),
+ getStyleDeclarationValue(textNode, "fill"),
+ (textNode as any).style?.fill
+ );
+ if (inlineFill) {
+ return inlineFill;
+ }
+
+ const inlineColor = resolveFirstValidCSSColor(
+ textNode.getAttribute?.("color"),
+ getStyleDeclarationValue(textNode, "color"),
+ (textNode as any).style?.color
+ );
+ if (inlineColor) {
+ return inlineColor;
+ }
+
+ const explicitFallbackColor = resolveFirstValidCSSColor(fallbackColor);
+ if (explicitFallbackColor) {
+ return explicitFallbackColor;
+ }
+ return undefined;
+};
+
+/**
+ * Cleans an array of CSS style strings, removing !important from each value
+ * This is useful for processing style arrays from Mermaid data structures.
+ *
+ * @param styles - Array of CSS style strings (e.g., ["fill:#fff !important", "stroke:#000"])
+ * @returns Array of cleaned CSS style strings
+ *
+ * @example
+ * cleanCSSStyles(["fill:#fff !important", "stroke:#000"]) // => ["fill:#fff", "stroke:#000"]
+ */
+export const cleanCSSStyles = (styles: string[]): string[] => {
+ return styles
+ .flatMap((style) =>
+ parseCSSDeclarations(style).map(
+ ({ property, value }) => `${property}:${value}`
+ )
+ )
+ .filter((style) => style.length > 0);
+};
diff --git a/src/parser/er.ts b/src/parser/er.ts
new file mode 100644
index 00000000..509697ca
--- /dev/null
+++ b/src/parser/er.ts
@@ -0,0 +1,542 @@
+import { nanoid } from "nanoid";
+
+import {
+ Arrow,
+ CardinalityArrowhead,
+ Container,
+ Line,
+ Node,
+ Text,
+ createArrowSkeletion,
+ createTextSkeleton,
+} from "../elementSkeleton.js";
+import { entityCodesToText, getTransformAttr } from "../utils.js";
+import {
+ cleanCSSValue,
+ isValidCSSColor,
+ parseCSSDeclarations,
+ resolveElementTextColor,
+} from "./cssUtils.js";
+
+import type { ErDB } from "mermaid/dist/diagrams/er/erDb.js";
+import type { EntityNode } from "mermaid/dist/diagrams/er/erTypes.js";
+
+type ERLayoutEdge = {
+ id: string;
+ start: string;
+ end: string;
+ label?: string;
+ pattern?: string;
+ arrowTypeStart?: string;
+ arrowTypeEnd?: string;
+};
+
+type ParsedTextGroup = {
+ className: string;
+ text: string;
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ fontSize: number;
+ color?: string;
+};
+
+const ERD_TABLE_TEXT_FONT_SIZE = 18;
+
+export interface ERD {
+ type: "erd";
+ nodes: Array;
+ lines: Line[];
+ arrows: Arrow[];
+ text: Text[];
+}
+
+const parseStyleStrings = (styles?: string[]) => {
+ const styleObj: Record = {};
+ if (!styles) {
+ return styleObj;
+ }
+
+ styles.forEach((style) => {
+ parseCSSDeclarations(style).forEach(({ property, value }) => {
+ if (property && value) {
+ styleObj[property] = cleanCSSValue(value);
+ }
+ });
+ });
+
+ return styleObj;
+};
+
+const parseStrokeWidth = (value?: string | number | null) => {
+ if (value === null || value === undefined || value === "") {
+ return undefined;
+ }
+
+ const numericValue =
+ typeof value === "number" ? value : parseFloat(cleanCSSValue(value));
+
+ if (!Number.isFinite(numericValue) || numericValue <= 0) {
+ return undefined;
+ }
+
+ return numericValue;
+};
+
+const accumulateTranslation = (node: Element, stopAt?: Element | null) => {
+ let tx = 0;
+ let ty = 0;
+ let current: Element | null = node;
+
+ while (current && current !== stopAt) {
+ const { transformX, transformY } = getTransformAttr(current);
+ tx += transformX;
+ ty += transformY;
+ current = current.parentElement;
+ }
+
+ return { tx, ty };
+};
+
+const getTextContent = (node: Element) => {
+ const tspans = Array.from(node.querySelectorAll("tspan"));
+ const text = tspans.length
+ ? tspans
+ .map((span) => span.textContent?.trim())
+ .filter(Boolean)
+ .join("\n")
+ : node.textContent?.trim() || "";
+
+ return entityCodesToText(text);
+};
+
+const getFontSize = (node: Element) => {
+ const textNode =
+ node.querySelector("text, foreignObject, div, span, p") ||
+ (node as HTMLElement);
+ let fontSize = parseFloat(getComputedStyle(textNode).fontSize || "");
+
+ if (!Number.isFinite(fontSize) || fontSize <= 0) {
+ fontSize = Math.max(
+ 12,
+ (node as SVGGraphicsElement).getBBox().height * 0.75
+ );
+ }
+
+ return fontSize;
+};
+
+const parseTextGroup = (
+ group: SVGGraphicsElement,
+ containerEl: Element,
+ fallbackColor?: string
+): ParsedTextGroup | null => {
+ const text = getTextContent(group);
+ if (!text) {
+ return null;
+ }
+
+ const bbox = group.getBBox();
+ const { tx, ty } = accumulateTranslation(group, containerEl);
+
+ return {
+ className: group.getAttribute("class") || "",
+ text,
+ x: bbox.x + tx,
+ y: bbox.y + ty,
+ width: bbox.width,
+ height: bbox.height,
+ fontSize: getFontSize(group),
+ color: resolveElementTextColor(group, fallbackColor),
+ };
+};
+
+const getPathCoordinates = (path: SVGPathElement) => {
+ const dAttr = path.getAttribute("d");
+ if (!dAttr) {
+ return null;
+ }
+
+ const numericTokens = Array.from(
+ dAttr.matchAll(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi),
+ (match) => Number(match[0])
+ );
+
+ if (numericTokens.length < 4) {
+ return null;
+ }
+
+ return {
+ startX: numericTokens[0],
+ startY: numericTokens[1],
+ endX: numericTokens[numericTokens.length - 2],
+ endY: numericTokens[numericTokens.length - 1],
+ };
+};
+
+const getDividerLine = (
+ dividerNode: SVGPathElement | SVGLineElement,
+ containerEl: Element,
+ groupId: string | undefined,
+ entityId: string,
+ fallbackStrokeColor?: string,
+ fallbackStrokeWidth?: number,
+ fallbackStrokeStyle?: Line["strokeStyle"]
+): Line | null => {
+ const { tx, ty } = accumulateTranslation(dividerNode, containerEl);
+ let startX = 0;
+ let startY = 0;
+ let endX = 0;
+ let endY = 0;
+
+ if (dividerNode.tagName.toLowerCase() === "line") {
+ startX = Number(dividerNode.getAttribute("x1")) + tx;
+ startY = Number(dividerNode.getAttribute("y1")) + ty;
+ endX = Number(dividerNode.getAttribute("x2")) + tx;
+ endY = Number(dividerNode.getAttribute("y2")) + ty;
+ } else {
+ const coords = getPathCoordinates(dividerNode);
+ if (!coords) {
+ return null;
+ }
+
+ startX = coords.startX + tx;
+ startY = coords.startY + ty;
+ endX = coords.endX + tx;
+ endY = coords.endY + ty;
+ }
+
+ const line: Line = {
+ type: "line",
+ id: nanoid(),
+ groupId,
+ startX,
+ startY,
+ endX,
+ endY,
+ metadata: { entityId },
+ };
+
+ if (
+ fallbackStrokeColor &&
+ isValidCSSColor(fallbackStrokeColor) &&
+ fallbackStrokeColor !== "none"
+ ) {
+ line.strokeColor = fallbackStrokeColor;
+ }
+ if (fallbackStrokeWidth !== undefined) {
+ line.strokeWidth = fallbackStrokeWidth;
+ }
+ if (fallbackStrokeStyle) {
+ line.strokeStyle = fallbackStrokeStyle;
+ }
+
+ return line;
+};
+
+const getCardinalityArrowhead = (
+ arrowType?: string
+): CardinalityArrowhead | null => {
+ switch (arrowType?.toLowerCase()) {
+ case "one":
+ return "cardinality_one";
+ case "many":
+ return "cardinality_many";
+ case "only_one":
+ return "cardinality_exactly_one";
+ case "one_or_more":
+ return "cardinality_one_or_many";
+ case "zero_or_one":
+ return "cardinality_zero_or_one";
+ case "zero_or_more":
+ return "cardinality_zero_or_many";
+ default:
+ return null;
+ }
+};
+
+const getStrokeStyle = (pattern?: string) => {
+ switch (pattern) {
+ case "dotted":
+ return "dotted" as const;
+ case "dashed":
+ return "dashed" as const;
+ default:
+ return "solid" as const;
+ }
+};
+
+const getDecodedEdgePoints = (
+ edgePath: SVGPathElement
+): Array<{ x: number; y: number }> => {
+ const encodedPoints = edgePath.getAttribute("data-points");
+ if (!encodedPoints) {
+ const coords = getPathCoordinates(edgePath);
+ return coords
+ ? [
+ { x: coords.startX, y: coords.startY },
+ { x: coords.endX, y: coords.endY },
+ ]
+ : [];
+ }
+
+ try {
+ const decoded = atob(encodedPoints);
+ const points = JSON.parse(decoded);
+ return Array.isArray(points)
+ ? points.filter(
+ (point): point is { x: number; y: number } =>
+ point &&
+ typeof point.x === "number" &&
+ typeof point.y === "number" &&
+ Number.isFinite(point.x) &&
+ Number.isFinite(point.y)
+ )
+ : [];
+ } catch {
+ return [];
+ }
+};
+
+const getRelationshipPaths = (
+ edge: ERLayoutEdge,
+ containerEl: Element
+): SVGPathElement[] => {
+ const directPath = containerEl.querySelector(
+ `path[id="${edge.id}"][data-edge="true"]`
+ );
+ if (directPath) {
+ return [directPath];
+ }
+
+ if (edge.start !== edge.end) {
+ return [];
+ }
+
+ const cyclicPathIds = [
+ `${edge.start}-cyclic-special-1`,
+ `${edge.start}-cyclic-special-mid`,
+ `${edge.start}-cyclic-special-2`,
+ ];
+
+ return cyclicPathIds
+ .map((pathId) =>
+ containerEl.querySelector(
+ `path[id="${pathId}"][data-edge="true"]`
+ )
+ )
+ .filter((path): path is SVGPathElement => path !== null);
+};
+
+const mergeEdgePoints = (edgePaths: SVGPathElement[]) => {
+ const mergedPoints: Array<{ x: number; y: number }> = [];
+
+ edgePaths.forEach((edgePath) => {
+ getDecodedEdgePoints(edgePath).forEach((point) => {
+ const previousPoint = mergedPoints[mergedPoints.length - 1];
+ if (
+ previousPoint &&
+ previousPoint.x === point.x &&
+ previousPoint.y === point.y
+ ) {
+ return;
+ }
+
+ mergedPoints.push(point);
+ });
+ });
+
+ return mergedPoints;
+};
+
+const parseEntity = (
+ entity: EntityNode,
+ containerEl: Element
+): { container: Container; lines: Line[]; text: Text[] } => {
+ const domNode = containerEl.querySelector(`[id="${entity.id}"]`);
+ if (!domNode) {
+ throw new Error(`ER entity ${entity.id} not found in rendered SVG`);
+ }
+
+ const groupId = entity.attributes.length ? nanoid() : undefined;
+ const bbox = domNode.getBBox();
+ const { tx, ty } = accumulateTranslation(domNode, containerEl);
+ const nodeStyle = parseStyleStrings([
+ ...(entity.cssStyles || []),
+ ...(entity.cssCompiledStyles || []),
+ ]);
+ const fill = cleanCSSValue(nodeStyle.fill || "");
+ const stroke = cleanCSSValue(nodeStyle.stroke || "");
+ const strokeWidth = parseStrokeWidth(nodeStyle["stroke-width"]);
+ const dashArray = cleanCSSValue(nodeStyle["stroke-dasharray"] || "");
+
+ const labelGroups = Array.from(
+ domNode.querySelectorAll("g.label")
+ )
+ .map((group) => parseTextGroup(group, containerEl, nodeStyle.color))
+ .filter((group): group is ParsedTextGroup => group !== null);
+
+ const titleGroup =
+ labelGroups.find((group) => group.className.includes("name")) ||
+ labelGroups[0];
+ const attributeGroups = labelGroups.filter((group) => group !== titleGroup);
+
+ const titleText =
+ titleGroup?.text || entityCodesToText(entity.alias || entity.label || "");
+
+ const container: Container = {
+ type: "rectangle",
+ id: entity.id,
+ groupId,
+ x: bbox.x + tx,
+ y: bbox.y + ty,
+ width: bbox.width,
+ height: bbox.height,
+ label: {
+ text: titleText,
+ fontSize: entity.attributes.length
+ ? ERD_TABLE_TEXT_FONT_SIZE
+ : titleGroup?.fontSize || 16,
+ color: titleGroup?.color,
+ textAlign: "center",
+ verticalAlign: entity.attributes.length ? "top" : "middle",
+ },
+ metadata: {
+ entityId: entity.id,
+ entityLabel: entity.label,
+ entityAlias: entity.alias,
+ },
+ };
+
+ if (isValidCSSColor(fill) && fill !== "none") {
+ container.bgColor = fill;
+ }
+ if (isValidCSSColor(stroke) && stroke !== "none") {
+ container.strokeColor = stroke;
+ }
+ if (strokeWidth && Number.isFinite(strokeWidth) && strokeWidth > 0) {
+ container.strokeWidth = strokeWidth;
+ }
+ if (dashArray && dashArray !== "none") {
+ container.strokeStyle = "dashed";
+ }
+
+ const lines = Array.from(
+ domNode.querySelectorAll(
+ ".divider path, path.divider, line.divider"
+ )
+ )
+ .map((dividerNode) =>
+ getDividerLine(
+ dividerNode,
+ containerEl,
+ groupId,
+ entity.id,
+ container.strokeColor,
+ container.strokeWidth,
+ container.strokeStyle
+ )
+ )
+ .filter((line): line is Line => line !== null);
+
+ const text = attributeGroups.map((group) =>
+ createTextSkeleton(group.x, group.y, group.text, {
+ id: nanoid(),
+ groupId,
+ width: group.width,
+ height: group.height,
+ fontSize: ERD_TABLE_TEXT_FONT_SIZE,
+ color: group.color,
+ metadata: { entityId: entity.id },
+ })
+ );
+
+ return { container, lines, text };
+};
+
+const parseRelationship = (edge: ERLayoutEdge, containerEl: Element): Arrow => {
+ const edgePaths = getRelationshipPaths(edge, containerEl);
+ if (!edgePaths.length) {
+ throw new Error(`ER relationship ${edge.id} not found in rendered SVG`);
+ }
+
+ const points = mergeEdgePoints(edgePaths);
+ if (points.length < 2) {
+ throw new Error(`ER relationship ${edge.id} is missing usable path points`);
+ }
+
+ const startPoint = points[0];
+ const endPoint = points[points.length - 1];
+ const edgePath = edgePaths[0];
+ const strokeColor = cleanCSSValue(
+ edgePath.getAttribute("stroke") || getComputedStyle(edgePath).stroke || ""
+ );
+ const strokeWidth = Number(
+ edgePath.getAttribute("stroke-width") ||
+ getComputedStyle(edgePath).strokeWidth ||
+ 1
+ );
+
+ const arrow = createArrowSkeletion(
+ startPoint.x,
+ startPoint.y,
+ endPoint.x,
+ endPoint.y,
+ {
+ id: edge.id,
+ label: edge.label
+ ? {
+ text: entityCodesToText(edge.label),
+ fontSize: 16,
+ textAlign: "center",
+ }
+ : undefined,
+ strokeStyle: getStrokeStyle(edge.pattern),
+ startArrowhead: getCardinalityArrowhead(edge.arrowTypeStart),
+ endArrowhead: getCardinalityArrowhead(edge.arrowTypeEnd),
+ start: { type: "rectangle", id: edge.start },
+ end: { type: "rectangle", id: edge.end },
+ points: points.map((point) => [
+ point.x - startPoint.x,
+ point.y - startPoint.y,
+ ]),
+ }
+ );
+
+ if (isValidCSSColor(strokeColor) && strokeColor !== "none") {
+ arrow.strokeColor = strokeColor;
+ }
+ if (Number.isFinite(strokeWidth) && strokeWidth > 0) {
+ arrow.strokeWidth = strokeWidth;
+ }
+
+ return arrow;
+};
+
+export const parseMermaidERDiagram = (db: ErDB, containerEl: Element): ERD => {
+ const data = db.getData();
+ const entities = data.nodes as unknown as EntityNode[];
+ const edges = data.edges as unknown as ERLayoutEdge[];
+
+ const containers: Container[] = [];
+ const lines: Line[] = [];
+ const text: Text[] = [];
+
+ entities.forEach((entity) => {
+ const parsedEntity = parseEntity(entity, containerEl);
+ containers.push(parsedEntity.container);
+ lines.push(...parsedEntity.lines);
+ text.push(...parsedEntity.text);
+ });
+
+ const arrows = edges.map((edge) => parseRelationship(edge, containerEl));
+
+ return {
+ type: "erd",
+ nodes: [containers],
+ lines,
+ arrows,
+ text,
+ };
+};
diff --git a/src/parser/flowchart.ts b/src/parser/flowchart.ts
index ee096534..08d900b8 100644
--- a/src/parser/flowchart.ts
+++ b/src/parser/flowchart.ts
@@ -4,15 +4,27 @@ import {
getTransformAttr,
} from "../utils.js";
import {
+ ContainerStyle,
CONTAINER_STYLE_PROPERTY,
LABEL_STYLE_PROPERTY,
+ LabelStyle,
Position,
SubGraph,
Vertex,
} from "../interfaces.js";
-import type { Diagram } from "mermaid/dist/Diagram.js";
-import { DiagramStyleClassDef } from "mermaid/dist/diagram-api/types.js";
+import type { FlowDB } from "mermaid/dist/diagrams/flowchart/flowDb.js";
+import type {
+ FlowVertex,
+ FlowEdge,
+ FlowClass,
+ FlowSubGraph,
+} from "mermaid/dist/diagrams/flowchart/types.js";
+import {
+ cleanCSSValue,
+ isValidCSSColor,
+ parseCSSDeclarations,
+} from "./cssUtils.js";
export interface Flowchart {
type: "flowchart";
@@ -25,10 +37,10 @@ export interface Edge {
id?: string;
start: string;
end: string;
- type: string;
+ type?: string;
text: string;
labelType: string;
- stroke: string;
+ stroke?: string;
startX: number;
startY: number;
endX: number;
@@ -36,7 +48,147 @@ export interface Edge {
reflectionPoints: Position[];
}
-const parseSubGraph = (data: any, containerEl: Element): SubGraph => {
+const applyContainerStyleProperty = (
+ style: ContainerStyle,
+ key: string,
+ value: string
+) => {
+ switch (key) {
+ case CONTAINER_STYLE_PROPERTY.FILL:
+ case CONTAINER_STYLE_PROPERTY.STROKE:
+ if (isValidCSSColor(value)) {
+ style[key] = value;
+ }
+ break;
+ case CONTAINER_STYLE_PROPERTY.STROKE_WIDTH:
+ case CONTAINER_STYLE_PROPERTY.STROKE_DASHARRAY:
+ style[key] = value;
+ break;
+ }
+};
+
+const applyLabelStyleProperty = (
+ style: LabelStyle,
+ key: string,
+ value: string
+) => {
+ if (key === LABEL_STYLE_PROPERTY.COLOR && isValidCSSColor(value)) {
+ style[LABEL_STYLE_PROPERTY.COLOR] = value;
+ }
+};
+
+const applyStyleTextToStyles = (
+ styleText: string | null | undefined,
+ containerStyle: ContainerStyle,
+ labelStyle: LabelStyle
+) => {
+ if (!styleText) {
+ return;
+ }
+
+ parseCSSDeclarations(styleText).forEach(({ property, value }) => {
+ applyContainerStyleProperty(containerStyle, property, value);
+ applyLabelStyleProperty(labelStyle, property, value);
+ });
+};
+
+const applyStyleTextToLabelStyle = (
+ styleText: string | null | undefined,
+ labelStyle: LabelStyle
+) => {
+ if (!styleText) {
+ return;
+ }
+
+ parseCSSDeclarations(styleText).forEach(({ property, value }) => {
+ if (property === "fill" && isValidCSSColor(value)) {
+ labelStyle[LABEL_STYLE_PROPERTY.COLOR] = value;
+ return;
+ }
+
+ applyLabelStyleProperty(labelStyle, property, value);
+ });
+};
+
+const applyElementAttributesToContainerStyle = (
+ element: Element | null,
+ containerStyle: ContainerStyle
+) => {
+ if (!element) {
+ return;
+ }
+
+ const attrs: Array<[CONTAINER_STYLE_PROPERTY, string | null]> = [
+ [CONTAINER_STYLE_PROPERTY.FILL, element.getAttribute("fill")],
+ [CONTAINER_STYLE_PROPERTY.STROKE, element.getAttribute("stroke")],
+ [
+ CONTAINER_STYLE_PROPERTY.STROKE_WIDTH,
+ element.getAttribute("stroke-width"),
+ ],
+ [
+ CONTAINER_STYLE_PROPERTY.STROKE_DASHARRAY,
+ element.getAttribute("stroke-dasharray"),
+ ],
+ ];
+
+ attrs.forEach(([key, rawValue]) => {
+ const value = cleanCSSValue(rawValue || "");
+ if (value) {
+ applyContainerStyleProperty(containerStyle, key, value);
+ }
+ });
+};
+
+const applyElementAttributesToLabelStyle = (
+ element: Element | null,
+ labelStyle: LabelStyle
+) => {
+ if (!element) {
+ return;
+ }
+
+ const rawColor =
+ element.getAttribute("fill") || element.getAttribute("color");
+ const color = cleanCSSValue(rawColor || "");
+ if (isValidCSSColor(color)) {
+ labelStyle[LABEL_STYLE_PROPERTY.COLOR] = color;
+ }
+};
+
+const applyClassStyles = (
+ classId: string,
+ classes: Map | Record,
+ containerStyle: ContainerStyle,
+ labelStyle: LabelStyle
+) => {
+ if (!(classes instanceof Map)) {
+ return;
+ }
+
+ const classDef = classes.get(classId);
+ if (!classDef) {
+ return;
+ }
+
+ classDef.styles?.forEach((styleText) => {
+ parseCSSDeclarations(styleText).forEach(({ property, value }) => {
+ applyContainerStyleProperty(containerStyle, property, value);
+ applyLabelStyleProperty(labelStyle, property, value);
+ });
+ });
+
+ classDef.textStyles?.forEach((styleText) => {
+ parseCSSDeclarations(styleText).forEach(({ property, value }) => {
+ applyLabelStyleProperty(labelStyle, property, value);
+ });
+ });
+};
+
+const parseSubGraph = (
+ data: FlowSubGraph,
+ containerEl: Element,
+ classes: Map | Record
+): SubGraph => {
// Extract only node id for better reference
// e.g. full element id = "flowchart-c1-205" will map to "c1"
const nodeIds = data.nodes.map((n: string) => {
@@ -62,96 +214,120 @@ const parseSubGraph = (data: any, containerEl: Element): SubGraph => {
height: boundingBox.height,
};
- // Remove irrelevant properties
- data.classes = undefined;
- data.dir = undefined;
+ const containerStyle: SubGraph["containerStyle"] = {};
+ const labelStyle: SubGraph["labelStyle"] = {};
+
+ const shapeEl =
+ el.querySelector(
+ ":scope > rect, :scope > path, :scope > polygon, :scope > ellipse"
+ ) ||
+ el.querySelector(
+ ".cluster > rect, .cluster > path, .cluster > polygon, .cluster > ellipse"
+ ) ||
+ el.querySelector("rect, path, polygon, ellipse");
+
+ applyStyleTextToStyles(el.getAttribute("style"), containerStyle, labelStyle);
+ applyStyleTextToStyles(
+ shapeEl?.getAttribute("style"),
+ containerStyle,
+ labelStyle
+ );
+ applyElementAttributesToContainerStyle(shapeEl, containerStyle);
+
+ const labelEl =
+ el.querySelector(".cluster-label text, .cluster-label tspan") ||
+ el.querySelector("text");
+ applyStyleTextToLabelStyle(labelEl?.getAttribute("style"), labelStyle);
+ applyElementAttributesToLabelStyle(labelEl, labelStyle);
+
+ applyClassStyles(data.id, classes, containerStyle, labelStyle);
+ data.classes?.forEach((classId) => {
+ applyClassStyles(classId, classes, containerStyle, labelStyle);
+ });
return {
- ...data,
+ id: data.id,
nodeIds,
+ text: entityCodesToText(data.title),
+ labelType: "text",
...position,
...dimension,
- text: entityCodesToText(data.title),
+ containerStyle,
+ labelStyle,
};
};
const parseVertex = (
- data: any,
+ vertex: FlowVertex,
containerEl: Element,
- classes: { [key: string]: DiagramStyleClassDef }
+ classes: Map | Record
): Vertex | undefined => {
// Find Vertex element
- const el: SVGSVGElement | null = containerEl.querySelector(
- `[id*="flowchart-${data.id}-"]`
+ const node: SVGSVGElement | null = containerEl.querySelector(
+ `[id*="${vertex.domId}"]`
);
- if (!el) {
+ if (!node) {
return undefined;
}
// Check if Vertex attached with link
let link;
- if (el.parentElement?.tagName.toLowerCase() === "a") {
- link = el.parentElement.getAttribute("xlink:href");
+ if (node.parentElement?.tagName.toLowerCase() === "a") {
+ link = node.parentElement.getAttribute("xlink:href");
}
// Get position
const position = computeElementPosition(
- link ? el.parentElement : el,
+ link ? node.parentElement : node,
containerEl
);
// Get dimension
- const boundingBox = el.getBBox();
+ const boundingBox = node.getBBox();
const dimension = {
width: boundingBox.width,
height: boundingBox.height,
};
// Extract style
- const labelContainerStyleText = el
- .querySelector(".label-container")
- ?.getAttribute("style");
- const labelStyleText = el.querySelector(".label")?.getAttribute("style");
-
const containerStyle: Vertex["containerStyle"] = {};
- labelContainerStyleText?.split(";").forEach((property) => {
- if (!property) {
- return;
- }
+ const labelStyle: Vertex["labelStyle"] = {};
+
+ if (vertex.classes && classes instanceof Map) {
+ (Array.isArray(vertex.classes) ? vertex.classes : [vertex.classes]).forEach(
+ (classId) => {
+ applyClassStyles(classId, classes, containerStyle, labelStyle);
+ }
+ );
+ }
- const key = property.split(":")[0].trim() as CONTAINER_STYLE_PROPERTY;
- const value = property.split(":")[1].trim();
- containerStyle[key] = value;
+ vertex.styles?.forEach((styleText) => {
+ applyStyleTextToStyles(styleText, containerStyle, labelStyle);
});
- const labelStyle: Vertex["labelStyle"] = {};
- labelStyleText?.split(";").forEach((property) => {
- if (!property) {
- return;
- }
+ const shapeEl = node.querySelector(".label-container");
+ applyStyleTextToStyles(
+ shapeEl?.getAttribute("style"),
+ containerStyle,
+ labelStyle
+ );
+ applyElementAttributesToContainerStyle(shapeEl, containerStyle);
- const key = property.split(":")[0].trim() as LABEL_STYLE_PROPERTY;
- const value = property.split(":")[1].trim();
- labelStyle[key] = value;
+ const labelElements = Array.from(
+ node.querySelectorAll(
+ ".label, .nodeLabel, .label text, .label tspan, .label span, .label div"
+ )
+ );
+
+ labelElements.forEach((element) => {
+ applyStyleTextToLabelStyle(element.getAttribute("style"), labelStyle);
+ applyElementAttributesToLabelStyle(element, labelStyle);
});
- if (data.classes) {
- const classDef = classes[data.classes];
- if (classDef) {
- classDef.styles?.forEach((style) => {
- const [key, value] = style.split(":");
- containerStyle[key.trim() as CONTAINER_STYLE_PROPERTY] = value.trim();
- });
- classDef.textStyles?.forEach((style) => {
- const [key, value] = style.split(":");
- labelStyle[key.trim() as LABEL_STYLE_PROPERTY] = value.trim();
- });
- }
- }
return {
- id: data.id,
- labelType: data.labelType,
- text: entityCodesToText(data.text),
- type: data.type,
+ id: vertex.id,
+ labelType: vertex.labelType,
+ text: entityCodesToText(vertex.text || ""),
+ type: vertex.type as any,
link: link || undefined,
...position,
...dimension,
@@ -161,30 +337,28 @@ const parseVertex = (
};
const parseEdge = (
- data: any,
+ edge: FlowEdge,
edgeIndex: number,
containerEl: Element
): Edge => {
// Find edge element
- const edge = containerEl.querySelector(
- `[id*="L-${data.start}-${data.end}-${edgeIndex}"]`
- );
+ const node = containerEl.querySelector(`[id*="${edge.id}"]`);
- if (!edge) {
+ if (!node) {
throw new Error("Edge element not found");
}
// Compute edge position data
- const position = computeElementPosition(edge, containerEl);
- const edgePositionData = computeEdgePositions(edge, position);
+ const position = computeElementPosition(node, containerEl);
+ const edgePositionData = computeEdgePositions(node, position);
// Remove irrelevant properties
- data.length = undefined;
+ edge.length = undefined;
return {
- ...data,
+ ...edge,
...edgePositionData,
- text: entityCodesToText(data.text),
+ text: entityCodesToText(edge.text),
};
};
@@ -236,44 +410,67 @@ const computeElementPosition = (
};
export const parseMermaidFlowChartDiagram = (
- diagram: Diagram,
+ db: FlowDB,
containerEl: Element
): Flowchart => {
- // This does some cleanup and initialization making sure
- // diagram is parsed correctly. Useful when multiple diagrams are
- // parsed together one after another, eg in playground
- // https://github.com/mermaid-js/mermaid/blob/e561cbd3be2a93b8bedfa4839484966faad92ccf/packages/mermaid/src/Diagram.ts#L43
- diagram.parse();
-
- // Get mermaid parsed data from parser shared variable `yy`
- //@ts-ignore
- const mermaidParser = diagram.parser.yy;
- const vertices = mermaidParser.getVertices();
- const classes = mermaidParser.getClasses();
- Object.keys(vertices).forEach((id) => {
- vertices[id] = parseVertex(vertices[id], containerEl, classes);
- });
+ // Get mermaid parsed data from the diagram's database (Mermaid v11 API)
+ const verticesData = db.getVertices();
+ const edgesData = db.getEdges();
+ const subGraphsData = db.getSubGraphs();
+ const classesData = db.getClasses();
+
+ // Parse vertices
+ const vertices: Record = {};
+
+ // Normalize classesData - it can be a Map or empty object {}
+ const normalizedClasses: Map | Record =
+ classesData instanceof Map ? classesData : {};
+
+ // In v11, getVertices() returns a Map
+ if (verticesData instanceof Map) {
+ verticesData.forEach((vertex, id) => {
+ vertices[id] = parseVertex(vertex, containerEl, normalizedClasses);
+ });
+ } else if (typeof verticesData === "object" && verticesData !== null) {
+ // Fallback for object-based return
+ Object.entries(verticesData).forEach(([id, vertex]) => {
+ vertices[id] = parseVertex(
+ vertex as FlowVertex,
+ containerEl,
+ normalizedClasses
+ );
+ });
+ }
// Track the count of edges based on the edge id
const edgeCountMap = new Map();
- const edges = mermaidParser
- .getEdges()
- .filter((edge: any) => {
- // Sometimes mermaid parser returns edges which are not present in the DOM hence this is a safety check to only consider edges present in the DOM, issue - https://github.com/mermaid-js/mermaid/issues/5516
- return containerEl.querySelector(`[id*="L-${edge.start}-${edge.end}"]`);
+ const edges: Edge[] = (Array.isArray(edgesData) ? edgesData : [])
+ .map((edge: any) => {
+ // Filter out edges not found in the DOM
+ if (!containerEl.querySelector(`[id*="${edge.id}"]`)) {
+ return null;
+ }
+
+ // Calculate index for edges between the same two nodes
+ const edgeMapKey = `${edge.start}-${edge.end}`;
+ const count = edgeCountMap.get(edgeMapKey) || 0;
+ edgeCountMap.set(edgeMapKey, count + 1);
+
+ return parseEdge(edge as FlowEdge, count, containerEl);
})
- .map((data: any) => {
- const edgeId = `${data.start}-${data.end}`;
-
- const count = edgeCountMap.get(edgeId) || 0;
- edgeCountMap.set(edgeId, count + 1);
-
- return parseEdge(data, count, containerEl);
- });
-
- const subGraphs = mermaidParser
- .getSubGraphs()
- .map((data: any) => parseSubGraph(data, containerEl));
+ .filter(
+ (edge): edge is Edge => edge !== null && edge.reflectionPoints.length > 1
+ );
+
+ const subGraphs = (Array.isArray(subGraphsData) ? subGraphsData : []).map(
+ (subgraph: any) => {
+ return parseSubGraph(
+ subgraph as FlowSubGraph,
+ containerEl,
+ normalizedClasses
+ );
+ }
+ );
return {
type: "flowchart",
diff --git a/src/parser/sequence.ts b/src/parser/sequence.ts
index ff2eb560..08ae9134 100644
--- a/src/parser/sequence.ts
+++ b/src/parser/sequence.ts
@@ -11,9 +11,10 @@ import {
createLineSkeletonFromSVG,
createTextSkeletonFromSVG,
} from "../elementSkeleton.js";
+import { cleanCSSValue } from "./cssUtils.js";
import type { Diagram } from "mermaid/dist/Diagram.js";
-import type { StrokeStyle } from "@excalidraw/excalidraw/types/element/types.js";
+import type { StrokeStyle } from "@excalidraw/excalidraw/element/types";
type ARROW_KEYS = keyof typeof SEQUENCE_ARROW_TYPES;
@@ -28,6 +29,13 @@ type Group = {
actorKeys: Array;
fill: string;
};
+
+type MermaidSequenceParser = {
+ getBoxes: () => Group[];
+ getActors: () => { [key: string]: Actor } | Map;
+ getMessages: () => Message[];
+};
+
export interface Sequence {
type: "sequence";
nodes: Array;
@@ -50,6 +58,12 @@ type Actor = {
description: string;
type: "actor" | "participant";
};
+
+type ParsedActor = {
+ topId?: string;
+ bottomId?: string;
+ bindType?: "rectangle" | "ellipse";
+};
// Currently mermaid supported these 6 arrow types, the names are taken from mermaidParser.LINETYPE
const SEQUENCE_ARROW_TYPES = {
0: "SOLID",
@@ -146,7 +160,7 @@ const attachSequenceNumberToArrow = (
};
const createActorSymbol = (
- rootNode: SVGRectElement,
+ rootNode: SVGElement,
text: string,
opts?: { id?: string }
) => {
@@ -208,7 +222,29 @@ const createActorSymbol = (
return nodeElements;
};
-const parseActor = (actors: { [key: string]: Actor }, containerEl: Element) => {
+const applyRectStyles = (container: Container, rect: Element) => {
+ const fill = rect.getAttribute("fill");
+ const stroke = rect.getAttribute("stroke");
+ const strokeWidth = rect.getAttribute("stroke-width");
+ const dashArray = rect.getAttribute("stroke-dasharray");
+ if (fill && fill !== "none") {
+ container.bgColor = cleanCSSValue(fill);
+ }
+ if (stroke && stroke !== "none") {
+ container.strokeColor = cleanCSSValue(stroke);
+ }
+ if (strokeWidth) {
+ container.strokeWidth = Number(strokeWidth);
+ }
+ if (dashArray && dashArray.trim()) {
+ container.strokeStyle = "dashed";
+ }
+};
+
+const parseActor = (
+ actors: { [key: string]: Actor } | Map,
+ containerEl: Element
+): { nodes: Array; lines: Array; actorMap: Record } => {
const actorTopNodes = Array.from(
containerEl.querySelectorAll(".actor-top")
);
@@ -218,13 +254,47 @@ const parseActor = (actors: { [key: string]: Actor }, containerEl: Element) => {
const nodes: Array = [];
const lines: Array = [];
- Object.values(actors).forEach((actor, index) => {
+ const actorMap: Record = {};
+ const actorList =
+ actors instanceof Map ? Array.from(actors.values()) : Object.values(actors);
+ const actorLineNodes = Array.from(
+ containerEl.querySelectorAll(".actor-line")
+ );
+
+ const resolveActorLineNode = (
+ actor: Actor,
+ topRootNode: SVGElement
+ ): SVGLineElement | null => {
+ const actorName = actor.name;
+ const actorLineNode = actorLineNodes.find(
+ (lineNode) => lineNode.getAttribute("name") === actorName
+ );
+ if (actorLineNode) {
+ return actorLineNode;
+ }
+
+ // Fallback to DOM traversal in case Mermaid changes actor line markup/class.
+ const candidateNode =
+ actor.type === "participant"
+ ? topRootNode.parentElement?.previousElementSibling
+ : topRootNode.previousElementSibling;
+
+ if (!candidateNode) {
+ return null;
+ }
+ if (candidateNode.tagName === "line") {
+ return candidateNode as SVGLineElement;
+ }
+ return candidateNode.querySelector("line");
+ };
+
+ actorList.forEach((actor) => {
const topRootNode = actorTopNodes.find(
(actorNode) => actorNode.getAttribute("name") === actor.name
- ) as SVGRectElement;
+ ) as SVGElement;
const bottomRootNode = actorBottomNodes.find(
(actorNode) => actorNode.getAttribute("name") === actor.name
- ) as SVGRectElement;
+ ) as SVGElement;
if (!topRootNode || !bottomRootNode) {
throw "root not found";
@@ -233,10 +303,11 @@ const parseActor = (actors: { [key: string]: Actor }, containerEl: Element) => {
if (actor.type === "participant") {
// creating top actor node element
const topNodeElement = createContainerSkeletonFromSVG(
- topRootNode,
+ topRootNode as SVGRectElement,
"rectangle",
{ id: `${actor.name}-top`, label: { text }, subtype: "actor" }
);
+ applyRectStyles(topNodeElement, topRootNode);
if (!topNodeElement) {
throw "Top Node element not found!";
}
@@ -244,15 +315,19 @@ const parseActor = (actors: { [key: string]: Actor }, containerEl: Element) => {
// creating bottom actor node element
const bottomNodeElement = createContainerSkeletonFromSVG(
- bottomRootNode,
+ bottomRootNode as SVGRectElement,
"rectangle",
{ id: `${actor.name}-bottom`, label: { text }, subtype: "actor" }
);
+ actorMap[actor.name] = {
+ topId: `${actor.name}-top`,
+ bottomId: `${actor.name}-bottom`,
+ bindType: "rectangle",
+ };
+ applyRectStyles(bottomNodeElement, bottomRootNode);
nodes.push([bottomNodeElement]);
- // Get the line connecting the top and bottom nodes. As per the DOM, the line is rendered as sibling parent of top root node
- const lineNode = topRootNode?.parentElement
- ?.previousElementSibling as SVGLineElement;
+ const lineNode = resolveActorLineNode(actor, topRootNode);
if (lineNode?.tagName !== "line") {
throw "Line not found";
@@ -282,9 +357,7 @@ const parseActor = (actors: { [key: string]: Actor }, containerEl: Element) => {
id: `${actor.name}-bottom`,
});
nodes.push(bottomNodeElement);
-
- // Get the line connecting the top and bottom nodes. As per the DOM, the line is rendered as sibling of the actor root element
- const lineNode = topRootNode.previousElementSibling as SVGLineElement;
+ const lineNode = resolveActorLineNode(actor, topRootNode);
if (lineNode?.tagName !== "line") {
throw "Line not found";
@@ -308,13 +381,31 @@ const parseActor = (actors: { [key: string]: Actor }, containerEl: Element) => {
);
lines.push(line);
}
+
+ const topEllipseNode = topNodeElement.find(
+ (node): node is Container => node.type === "ellipse"
+ );
+ const bottomEllipseActorNode = bottomNodeElement.find(
+ (node): node is Container => node.type === "ellipse"
+ );
+ if (topEllipseNode?.id && bottomEllipseActorNode?.id) {
+ actorMap[actor.name] = {
+ topId: topEllipseNode.id,
+ bottomId: bottomEllipseActorNode.id,
+ bindType: "ellipse",
+ };
+ }
}
});
- return { nodes, lines };
+ return { nodes, lines, actorMap };
};
-const computeArrows = (messages: Message[], containerEl: Element) => {
+const computeArrows = (
+ messages: Message[],
+ containerEl: Element,
+ actorMap: Record
+) => {
const arrows: Arrow[] = [];
const arrowNodes = Array.from(
@@ -336,6 +427,13 @@ const computeArrows = (messages: Message[], containerEl: Element) => {
? null
: "arrow",
});
+ // Attach to actors if available
+ const from = actorMap[message.from];
+ const to = actorMap[message.to];
+ if (from?.topId && to?.topId) {
+ arrow.start = { type: from.bindType || "rectangle", id: from.topId };
+ arrow.end = { type: to.bindType || "rectangle", id: to.topId };
+ }
attachSequenceNumberToArrow(arrowNode, arrow);
arrows.push(arrow);
});
@@ -360,6 +458,22 @@ const computeNotes = (messages: Message[], containerEl: Element) => {
label: { text },
subtype: "note",
});
+ const fill = rect.getAttribute("fill");
+ const stroke = rect.getAttribute("stroke");
+ const strokeWidth = rect.getAttribute("stroke-width");
+ const dashArray = rect.getAttribute("stroke-dasharray");
+ if (fill && fill !== "none") {
+ note.bgColor = cleanCSSValue(fill);
+ }
+ if (stroke && stroke !== "none") {
+ note.strokeColor = cleanCSSValue(stroke);
+ }
+ if (strokeWidth) {
+ note.strokeWidth = Number(strokeWidth);
+ }
+ if (dashArray && dashArray.trim()) {
+ note.strokeStyle = "dashed";
+ }
notes.push(note);
});
return notes;
@@ -375,6 +489,25 @@ const parseActivations = (containerEl: Element) => {
label: { text: "" },
subtype: "activation",
});
+ const applyRectStyles = () => {
+ const fill = node.getAttribute("fill");
+ const stroke = node.getAttribute("stroke");
+ const strokeWidth = node.getAttribute("stroke-width");
+ const dashArray = node.getAttribute("stroke-dasharray");
+ if (fill && fill !== "none") {
+ rect.bgColor = cleanCSSValue(fill);
+ }
+ if (stroke && stroke !== "none") {
+ rect.strokeColor = cleanCSSValue(stroke);
+ }
+ if (strokeWidth) {
+ rect.strokeWidth = Number(strokeWidth);
+ }
+ if (dashArray && dashArray.trim()) {
+ rect.strokeStyle = "dashed";
+ }
+ };
+ applyRectStyles();
activations.push(rect);
});
@@ -467,18 +600,22 @@ export const parseMermaidSequenceDiagram = (
diagram: Diagram,
containerEl: Element
): Sequence => {
- diagram.parse();
-
- // Get mermaid parsed data from parser shared variable `yy`
- //@ts-ignore
- const mermaidParser = diagram.parser.yy;
+ // Mermaid already parsed the diagram when creating `diagram`.
+ // Re-parsing here can duplicate sequence state (notably boxed participants).
+ const mermaidParser = diagram.db as MermaidSequenceParser;
const nodes: Array = [];
- const groups = mermaidParser.getBoxes();
+ const rawGroups = mermaidParser.getBoxes();
+ // Clean CSS values from groups to remove !important declarations
+ const groups = rawGroups.map((group: Group) => ({
+ ...group,
+ fill: cleanCSSValue(group.fill || ""),
+ }));
+
const bgHightlights = computeHighlights(containerEl);
const actorData = mermaidParser.getActors();
- const { nodes: actors, lines } = parseActor(actorData, containerEl);
+ const { nodes: actors, lines, actorMap } = parseActor(actorData, containerEl);
const messages = mermaidParser.getMessages();
- const arrows = computeArrows(messages, containerEl);
+ const arrows = computeArrows(messages, containerEl, actorMap);
const notes = computeNotes(messages, containerEl);
const activations = parseActivations(containerEl);
const loops = parseLoops(messages, containerEl);
diff --git a/src/types.ts b/src/types.ts
index 1212107f..ad607601 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,10 +1,10 @@
-import { ImportedDataState } from "@excalidraw/excalidraw/types/data/types.js";
-import {
+import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
+import type {
ExcalidrawRectangleElement,
ExcalidrawDiamondElement,
ExcalidrawEllipseElement,
-} from "@excalidraw/excalidraw/types/element/types.js";
-import { Mutable } from "@excalidraw/excalidraw/types/utility-types.js";
+} from "@excalidraw/excalidraw/element/types";
+import type { Mutable } from "@excalidraw/excalidraw/common/utility-types";
export type ArrayElement = A extends readonly (infer T)[] ? T : never;
diff --git a/src/utils.ts b/src/utils.ts
index 4cb1caf4..0df480f0 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -58,6 +58,35 @@ export const decodeEntities = function (text: string): string {
return text.replace(/fl°°/g, "#").replace(/fl°/g, "&").replace(/¶ß/g, ";");
};
+const DEFAULT_POINT_DEDUPE_THRESHOLD = 0.5;
+
+export const dedupeConsecutivePoints = (
+ points: readonly T[],
+ threshold = DEFAULT_POINT_DEDUPE_THRESHOLD
+): T[] => {
+ const dedupedPoints: T[] = [];
+
+ points.forEach((point) => {
+ const previousPoint = dedupedPoints[dedupedPoints.length - 1];
+ if (!previousPoint) {
+ dedupedPoints.push(point);
+ return;
+ }
+
+ const distance = Math.hypot(
+ point[0] - previousPoint[0],
+ point[1] - previousPoint[1]
+ );
+ if (distance <= threshold) {
+ return;
+ }
+
+ dedupedPoints.push(point);
+ });
+
+ return dedupedPoints;
+};
+
// Extract edge position start, end, and points (reflectionPoints)
interface EdgePositionData {
startX: number;
diff --git a/src/validateMermaid.ts b/src/validateMermaid.ts
deleted file mode 100644
index e7f4dea9..00000000
--- a/src/validateMermaid.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import mermaid from "mermaid";
-
-export async function validateMermaid(mermaidStr: string) {
- return mermaid.parse(mermaidStr, { suppressErrors: true });
-}
diff --git a/tests/cssUtils.test.ts b/tests/cssUtils.test.ts
new file mode 100644
index 00000000..3fa46f9e
--- /dev/null
+++ b/tests/cssUtils.test.ts
@@ -0,0 +1,52 @@
+import {
+ cleanCSSStyles,
+ isValidCSSColor,
+ parseCSSDeclarations,
+ resolveElementTextColor,
+} from "../src/parser/cssUtils.js";
+
+describe("cssUtils", () => {
+ it("splits whitespace-delimited declarations from Mermaid classDef styles", () => {
+ expect(parseCSSDeclarations("stroke:#00f stroke-width:2px")).toEqual([
+ { property: "stroke", value: "#00f" },
+ { property: "stroke-width", value: "2px" },
+ ]);
+ });
+
+ it("preserves commas and spaces inside CSS values", () => {
+ expect(
+ cleanCSSStyles([
+ "fill:rgb(191, 223, 255),stroke:#333,stroke-dasharray: 10 5",
+ ])
+ ).toEqual([
+ "fill:rgb(191, 223, 255)",
+ "stroke:#333",
+ "stroke-dasharray:10 5",
+ ]);
+ });
+
+ it("detects malformed CSS colors", () => {
+ expect(isValidCSSColor("#00f")).toBe(true);
+ expect(isValidCSSColor("rgb(191, 223, 255)")).toBe(true);
+ expect(isValidCSSColor("#00f stroke-width:2px")).toBe(false);
+ });
+
+ it("prefers an explicit fallback text color over default computed colors", () => {
+ const textNode = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "text"
+ );
+
+ expect(resolveElementTextColor(textNode, "#01579b")).toBe("#01579b");
+ });
+
+ it("prefers an explicit text fill over a fallback color", () => {
+ const textNode = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "text"
+ );
+ textNode.setAttribute("fill", "#e65100");
+
+ expect(resolveElementTextColor(textNode, "#01579b")).toBe("#e65100");
+ });
+});
diff --git a/tests/examples.test.ts b/tests/examples.test.ts
new file mode 100644
index 00000000..255c92ba
--- /dev/null
+++ b/tests/examples.test.ts
@@ -0,0 +1,966 @@
+import { graphToExcalidraw } from "../src/graphToExcalidraw.js";
+import { parseMermaid } from "../src/parseMermaid.js";
+import { DEFAULT_FONT_SIZE } from "../src/constants.js";
+import { isValidCSSColor } from "../src/parser/cssUtils.js";
+import { CLASS_DIAGRAM_TESTCASES } from "../playground/testcases/class.ts";
+import { ERD_DIAGRAM_TESTCASES } from "../playground/testcases/er.ts";
+import { FLOWCHART_DIAGRAM_TESTCASES } from "../playground/testcases/flowchart.ts";
+import { SEQUENCE_DIAGRAM_TESTCASES } from "../playground/testcases/sequence.ts";
+import { UNSUPPORTED_DIAGRAM_TESTCASES } from "../playground/testcases/unsupported.ts";
+
+const PLAYGROUND_TESTCASES = [
+ ...FLOWCHART_DIAGRAM_TESTCASES,
+ ...SEQUENCE_DIAGRAM_TESTCASES,
+ ...CLASS_DIAGRAM_TESTCASES,
+ ...ERD_DIAGRAM_TESTCASES,
+ ...UNSUPPORTED_DIAGRAM_TESTCASES,
+];
+
+type BBox = {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+};
+
+type ColorEntry = {
+ path: string;
+ value: string;
+};
+
+const zeroBox = (): BBox => ({ x: 0, y: 0, width: 0, height: 0 });
+
+const numberAttr = (element: Element, attribute: string, fallback = 0) => {
+ const rawValue = element.getAttribute(attribute);
+ return rawValue === null ? fallback : Number(rawValue);
+};
+
+const unionBoxes = (boxes: BBox[]): BBox => {
+ if (boxes.length === 0) {
+ return zeroBox();
+ }
+
+ const minX = Math.min(...boxes.map((box) => box.x));
+ const minY = Math.min(...boxes.map((box) => box.y));
+ const maxX = Math.max(...boxes.map((box) => box.x + box.width));
+ const maxY = Math.max(...boxes.map((box) => box.y + box.height));
+
+ return {
+ x: minX,
+ y: minY,
+ width: maxX - minX,
+ height: maxY - minY,
+ };
+};
+
+const parsePointsBox = (points: string | null): BBox => {
+ if (!points) {
+ return zeroBox();
+ }
+
+ const coordinates = points
+ .trim()
+ .split(/\s+/)
+ .map((pair) => pair.split(",").map((value) => Number(value)))
+ .filter(
+ (pair): pair is [number, number] =>
+ pair.length === 2 &&
+ Number.isFinite(pair[0]) &&
+ Number.isFinite(pair[1])
+ );
+
+ if (coordinates.length === 0) {
+ return zeroBox();
+ }
+
+ const xs = coordinates.map(([x]) => x);
+ const ys = coordinates.map(([, y]) => y);
+ const minX = Math.min(...xs);
+ const minY = Math.min(...ys);
+ const maxX = Math.max(...xs);
+ const maxY = Math.max(...ys);
+
+ return {
+ x: minX,
+ y: minY,
+ width: maxX - minX,
+ height: maxY - minY,
+ };
+};
+
+const parsePathBox = (pathData: string | null): BBox => {
+ if (!pathData) {
+ return zeroBox();
+ }
+
+ const numericTokens = Array.from(
+ pathData.matchAll(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi),
+ (match) => Number(match[0])
+ );
+
+ if (numericTokens.length < 2) {
+ return zeroBox();
+ }
+
+ const coordinates: Array<[number, number]> = [];
+ for (let index = 0; index < numericTokens.length - 1; index += 2) {
+ coordinates.push([numericTokens[index], numericTokens[index + 1]]);
+ }
+
+ const xs = coordinates.map(([x]) => x);
+ const ys = coordinates.map(([, y]) => y);
+ const minX = Math.min(...xs);
+ const minY = Math.min(...ys);
+ const maxX = Math.max(...xs);
+ const maxY = Math.max(...ys);
+
+ return {
+ x: minX,
+ y: minY,
+ width: maxX - minX,
+ height: maxY - minY,
+ };
+};
+
+const getMockBBox = (element: SVGElement): BBox => {
+ const tagName = element.tagName.toLowerCase();
+
+ switch (tagName) {
+ case "rect":
+ case "foreignobject":
+ case "image":
+ return {
+ x: numberAttr(element, "x"),
+ y: numberAttr(element, "y"),
+ width: numberAttr(element, "width"),
+ height: numberAttr(element, "height"),
+ };
+ case "circle": {
+ const r = numberAttr(element, "r");
+ const cx = numberAttr(element, "cx");
+ const cy = numberAttr(element, "cy");
+ return { x: cx - r, y: cy - r, width: r * 2, height: r * 2 };
+ }
+ case "ellipse": {
+ const rx = numberAttr(element, "rx");
+ const ry = numberAttr(element, "ry");
+ const cx = numberAttr(element, "cx");
+ const cy = numberAttr(element, "cy");
+ return { x: cx - rx, y: cy - ry, width: rx * 2, height: ry * 2 };
+ }
+ case "line": {
+ const x1 = numberAttr(element, "x1");
+ const y1 = numberAttr(element, "y1");
+ const x2 = numberAttr(element, "x2");
+ const y2 = numberAttr(element, "y2");
+ return {
+ x: Math.min(x1, x2),
+ y: Math.min(y1, y2),
+ width: Math.abs(x2 - x1),
+ height: Math.abs(y2 - y1),
+ };
+ }
+ case "polygon":
+ case "polyline":
+ return parsePointsBox(element.getAttribute("points"));
+ case "path":
+ return parsePathBox(element.getAttribute("d"));
+ case "text":
+ case "tspan": {
+ const x = numberAttr(element, "x");
+ const y = numberAttr(element, "y");
+ const text = element.textContent ?? "";
+ return {
+ x,
+ y,
+ width: Math.max(text.length * 8, 1),
+ height: 16,
+ };
+ }
+ default: {
+ const childBoxes = Array.from(element.children)
+ .filter((child): child is SVGElement => child instanceof SVGElement)
+ .map((child) => getMockBBox(child))
+ .filter((box) => box.width > 0 || box.height > 0);
+
+ return unionBoxes(childBoxes);
+ }
+ }
+};
+
+const collectColorEntries = (elements: any[]): ColorEntry[] => {
+ const entries: ColorEntry[] = [];
+
+ elements.forEach((element, index) => {
+ if (
+ element.type === "text" &&
+ typeof element.strokeColor === "string" &&
+ element.strokeColor
+ ) {
+ entries.push({
+ path: `elements[${index}].strokeColor`,
+ value: element.strokeColor,
+ });
+ } else if (typeof element.strokeColor === "string" && element.strokeColor) {
+ entries.push({
+ path: `elements[${index}].strokeColor`,
+ value: element.strokeColor,
+ });
+ }
+
+ if (
+ typeof element.backgroundColor === "string" &&
+ element.backgroundColor
+ ) {
+ entries.push({
+ path: `elements[${index}].backgroundColor`,
+ value: element.backgroundColor,
+ });
+ }
+
+ if (
+ element.label &&
+ typeof element.label.strokeColor === "string" &&
+ element.label.strokeColor
+ ) {
+ entries.push({
+ path: `elements[${index}].label.strokeColor`,
+ value: element.label.strokeColor,
+ });
+ }
+ });
+
+ return entries;
+};
+
+const hasConsecutiveDuplicatePoints = (
+ points: readonly (readonly [number, number])[]
+) => {
+ return points.some((point, index, array) => {
+ if (index === 0) {
+ return false;
+ }
+
+ const previousPoint = array[index - 1];
+ return point[0] === previousPoint[0] && point[1] === previousPoint[1];
+ });
+};
+
+describe("playground examples", () => {
+ const originalGetBBox = SVGElement.prototype.getBBox;
+ const originalGetBoundingClientRect =
+ SVGElement.prototype.getBoundingClientRect;
+ const originalGetComputedTextLength =
+ SVGElement.prototype.getComputedTextLength;
+
+ beforeAll(() => {
+ Object.defineProperty(SVGElement.prototype, "getBBox", {
+ configurable: true,
+ value: function getBBox() {
+ return getMockBBox(this);
+ },
+ });
+
+ Object.defineProperty(SVGElement.prototype, "getBoundingClientRect", {
+ configurable: true,
+ value: function getBoundingClientRect() {
+ const box = getMockBBox(this);
+ return {
+ ...box,
+ top: box.y,
+ right: box.x + box.width,
+ bottom: box.y + box.height,
+ left: box.x,
+ toJSON: () => box,
+ };
+ },
+ });
+
+ Object.defineProperty(SVGElement.prototype, "getComputedTextLength", {
+ configurable: true,
+ value: function getComputedTextLength() {
+ return Math.max((this.textContent ?? "").length * 8, 1);
+ },
+ });
+ });
+
+ afterAll(() => {
+ Object.defineProperty(SVGElement.prototype, "getBBox", {
+ configurable: true,
+ value: originalGetBBox,
+ });
+
+ Object.defineProperty(SVGElement.prototype, "getBoundingClientRect", {
+ configurable: true,
+ value: originalGetBoundingClientRect,
+ });
+
+ Object.defineProperty(SVGElement.prototype, "getComputedTextLength", {
+ configurable: true,
+ value: originalGetComputedTextLength,
+ });
+ });
+
+ it("preserves separate classDef stroke color and stroke width", async () => {
+ const definition = `flowchart LR
+ A:::foo & B:::bar --> C:::foobar
+ classDef foo stroke:#1971c2, fill:#4dabf7
+ classDef bar stroke:#d6336c, fill:#f783ac
+ classDef foobar stroke:#00f stroke-width:2px`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("flowchart");
+
+ const flowchart = graph;
+ expect(flowchart.vertices.C?.containerStyle.stroke).toBe("#00f");
+ expect(flowchart.vertices.C?.containerStyle["stroke-width"]).toBe("2px");
+ });
+
+ it("preserves direct flowchart node styling for fill, stroke, dash, and label color", async () => {
+ const definition = `flowchart LR
+id1(Start)-->id2(Stop)
+style id1 fill:#f9f,stroke:#333,stroke-width:4px
+style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("flowchart");
+
+ const flowchart = graph;
+ expect(flowchart.vertices.id1?.containerStyle).toMatchObject({
+ fill: "#f9f",
+ stroke: "#333",
+ "stroke-width": "4px",
+ });
+ expect(flowchart.vertices.id2?.containerStyle).toMatchObject({
+ fill: "#bbf",
+ stroke: "#f66",
+ "stroke-width": "2px",
+ "stroke-dasharray": "5 5",
+ });
+ expect(flowchart.vertices.id2?.labelStyle).toMatchObject({
+ color: "#fff",
+ });
+
+ const result = graphToExcalidraw(graph);
+ const startNode = result.elements.find(
+ (element: any) => element.id === "id1"
+ );
+ const stopNode = result.elements.find(
+ (element: any) => element.id === "id2"
+ );
+
+ expect(startNode).toMatchObject({
+ backgroundColor: "#f9f",
+ strokeColor: "#333",
+ strokeWidth: 4,
+ });
+ expect(stopNode).toMatchObject({
+ backgroundColor: "#bbf",
+ strokeColor: "#f66",
+ strokeWidth: 2,
+ strokeStyle: "dashed",
+ label: {
+ strokeColor: "#fff",
+ },
+ });
+ });
+
+ it("shrinks cylindrical flowchart node labels to avoid wrapping", async () => {
+ const graph = await parseMermaid(`flowchart LR
+id1[(Database)]`);
+ expect(graph.type).toBe("flowchart");
+
+ const result = graphToExcalidraw(graph);
+ const node = result.elements.find((element: any) => element.id === "id1");
+
+ expect(node).toMatchObject({
+ type: "rectangle",
+ label: {
+ text: "Database",
+ },
+ });
+ expect(node.label.fontSize).toBeLessThan(DEFAULT_FONT_SIZE);
+ expect(node.label.fontSize).toBeGreaterThanOrEqual(12);
+ });
+
+ it("keeps the default font size for non-cylindrical flowchart nodes", async () => {
+ const graph = await parseMermaid(`flowchart LR
+id1[Database]`);
+ expect(graph.type).toBe("flowchart");
+
+ const result = graphToExcalidraw(graph);
+ const node = result.elements.find((element: any) => element.id === "id1");
+
+ expect(node).toMatchObject({
+ type: "rectangle",
+ label: {
+ text: "Database",
+ fontSize: DEFAULT_FONT_SIZE,
+ },
+ });
+ });
+
+ it("uses rectangle labels for class headers while keeping members as text", async () => {
+ const definition = `classDiagram
+ class Duck
+ Duck : +String beakColor
+ Duck : +swim()
+ class Fish
+ Fish : -int sizeInFeet
+ Fish : +canEat()
+ class Zebra
+ Zebra : +bool is_wild
+ Zebra : +run()`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("class");
+
+ const result = graphToExcalidraw(graph);
+ const classRectangles = result.elements.filter(
+ (element: any) =>
+ element.type === "rectangle" &&
+ ["Duck", "Fish", "Zebra"].includes(element.id)
+ );
+
+ expect(classRectangles).toHaveLength(3);
+ expect(
+ classRectangles.map((element: any) => ({
+ id: element.id,
+ text: element.label?.text,
+ verticalAlign: element.label?.verticalAlign,
+ }))
+ ).toEqual([
+ { id: "Duck", text: "Duck", verticalAlign: "top" },
+ { id: "Fish", text: "Fish", verticalAlign: "top" },
+ { id: "Zebra", text: "Zebra", verticalAlign: "top" },
+ ]);
+
+ const textNodes = result.elements
+ .filter((element: any) => element.type === "text")
+ .map((element: any) => element.text);
+
+ expect(textNodes).not.toContain("Duck");
+ expect(textNodes).not.toContain("Fish");
+ expect(textNodes).not.toContain("Zebra");
+ expect(textNodes).toContain("+String beakColor");
+ expect(textNodes).toContain("-int sizeInFeet");
+ expect(textNodes).toContain("+bool is_wild");
+ });
+
+ it("preserves styled class text colors from direct style declarations", async () => {
+ const definition = `classDiagram
+ class User {
+ +String username
+ +String email
+ +login()
+ +logout()
+ }
+
+ class Profile {
+ +String bio
+ +String avatarUrl
+ +updateProfile()
+ }
+
+ class Post {
+ +int id
+ +String content
+ +DateTime createdAt
+ +publish()
+ }
+
+ class Comment {
+ +String text
+ +submit()
+ }
+
+ User "1" -- "1" Profile : has
+ User "1" -- "*" Post : creates
+ Post "1" -- "*" Comment : contains
+ User "1" -- "*" Comment : writes
+
+ style User fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#01579b
+ style Profile fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#4a148c
+ style Post fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px,color:#1b5e20
+ style Comment fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#e65100`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("class");
+
+ const result = graphToExcalidraw(graph);
+
+ expect(
+ result.elements.find(
+ (element: any) => element.type === "rectangle" && element.id === "User"
+ )
+ ).toMatchObject({
+ strokeColor: "#01579b",
+ label: {
+ strokeColor: "#01579b",
+ },
+ });
+
+ expect(
+ result.elements.find(
+ (element: any) =>
+ element.type === "text" &&
+ String(element.text).includes("+String username")
+ )
+ ).toMatchObject({
+ strokeColor: "#01579b",
+ });
+
+ expect(
+ result.elements.find(
+ (element: any) =>
+ element.type === "text" &&
+ String(element.text).includes("+String bio")
+ )
+ ).toMatchObject({
+ strokeColor: "#4a148c",
+ });
+
+ expect(
+ result.elements.find(
+ (element: any) =>
+ element.type === "text" && String(element.text).includes("+publish()")
+ )
+ ).toMatchObject({
+ strokeColor: "#1b5e20",
+ });
+
+ expect(
+ result.elements.find(
+ (element: any) =>
+ element.type === "text" && String(element.text).includes("+submit()")
+ )
+ ).toMatchObject({
+ strokeColor: "#e65100",
+ });
+ });
+
+ it("uses centered container labels for ERD entity headers while keeping attributes as text", async () => {
+ const definition = `erDiagram
+ PERSON ||--o{ CAR : owns
+ PERSON {
+ string driversLicense PK "The license #"
+ string firstName
+ string lastName
+ }
+ CAR {
+ string registrationNumber PK
+ string model
+ }`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("erd");
+
+ const result = graphToExcalidraw(graph);
+ const entityRectangles = result.elements.filter(
+ (element: any) => element.type === "rectangle"
+ );
+
+ expect(entityRectangles.length).toBeGreaterThanOrEqual(2);
+
+ const personRectangle = entityRectangles.find((element: any) =>
+ String(element.id).startsWith("entity-PERSON-")
+ );
+
+ expect(personRectangle).toMatchObject({
+ label: {
+ text: "PERSON",
+ textAlign: "center",
+ verticalAlign: "top",
+ },
+ });
+
+ const textNodes = result.elements
+ .filter((element: any) => element.type === "text")
+ .map((element: any) => element.text);
+
+ expect(textNodes).not.toContain("PERSON");
+ expect(textNodes).toContain("driversLicense");
+ expect(textNodes).toContain("registrationNumber");
+ expect(textNodes).toContain("The license #");
+ });
+
+ it("does not assign text colors to unstyled sequence diagrams", async () => {
+ const definition = `sequenceDiagram
+ actor Alice
+ actor Bob
+ Alice->>Bob: Hi Bob
+ Bob->>Alice: Hi Alice`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("sequence");
+
+ const result = graphToExcalidraw(graph);
+ const textElements = result.elements.filter(
+ (element: any) => element.type === "text"
+ );
+
+ expect(textElements.length).toBeGreaterThan(0);
+ textElements.forEach((element: any) => {
+ expect(element.strokeColor).toBeUndefined();
+ });
+ });
+
+ it("uses font size 18 for ERD entity table text without changing relationship labels", async () => {
+ const definition = `erDiagram
+ PERSON ||--o{ CAR : owns
+ PERSON {
+ string driversLicense PK "The license #"
+ string firstName
+ }
+ CAR {
+ string registrationNumber PK
+ }`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("erd");
+
+ const result = graphToExcalidraw(graph);
+ const personRectangle = result.elements.find(
+ (element: any) =>
+ element.type === "rectangle" &&
+ String(element.id).startsWith("entity-PERSON-")
+ );
+
+ expect(personRectangle).toMatchObject({
+ label: {
+ text: "PERSON",
+ fontSize: 18,
+ },
+ });
+
+ const entityTextNodes = result.elements.filter(
+ (element: any) => element.type === "text"
+ );
+ expect(entityTextNodes.length).toBeGreaterThan(0);
+ entityTextNodes.forEach((element: any) => {
+ expect(element.fontSize).toBe(18);
+ });
+
+ const relationshipArrow = result.elements.find(
+ (element: any) =>
+ element.type === "arrow" && element.label?.text === "owns"
+ );
+ expect(relationshipArrow).toMatchObject({
+ label: {
+ text: "owns",
+ fontSize: 16,
+ },
+ });
+ });
+
+ it("does not group single-entity ERD containers with only a bound label", async () => {
+ const definition = `erDiagram
+ PERSON`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("erd");
+
+ const result = graphToExcalidraw(graph);
+ const entityRectangle = result.elements.find(
+ (element: any) =>
+ element.type === "rectangle" &&
+ String(element.id).startsWith("entity-PERSON-")
+ );
+
+ expect(entityRectangle).toMatchObject({
+ label: {
+ text: "PERSON",
+ },
+ });
+ expect(entityRectangle?.groupIds).toBeUndefined();
+ expect(entityRectangle?.label?.groupIds).toBeUndefined();
+
+ const textNodes = result.elements.filter(
+ (element: any) => element.type === "text"
+ );
+ expect(textNodes).toHaveLength(0);
+ });
+
+ it("leaves ERD entity fill and stroke unset when Mermaid has no explicit styling", async () => {
+ const definition = `erDiagram
+ PERSON ||--o{ CAR : owns
+ PERSON {
+ string firstName
+ }
+ CAR {
+ string registrationNumber
+ }`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("erd");
+
+ const entityContainers = graph.nodes
+ .flat()
+ .filter(
+ (node: any) =>
+ node.type === "rectangle" && String(node.id).startsWith("entity-")
+ );
+
+ expect(entityContainers.length).toBeGreaterThanOrEqual(2);
+ entityContainers.forEach((container: any) => {
+ expect(container.bgColor).toBeUndefined();
+ expect(container.strokeColor).toBeUndefined();
+ expect(container.strokeWidth).toBeUndefined();
+ expect(container.strokeStyle).toBeUndefined();
+ });
+
+ expect(graph.lines.length).toBeGreaterThan(0);
+ graph.lines.forEach((line) => {
+ expect(line.strokeColor).toBeUndefined();
+ expect(line.strokeWidth).toBeUndefined();
+ expect(line.strokeStyle).toBeUndefined();
+ });
+ });
+
+ it("preserves explicit ERD entity styles from class definitions", async () => {
+ const definition = `erDiagram
+ PERSON {
+ string firstName
+ }
+ CAR {
+ string registrationNumber
+ }
+ PERSON:::foo ||--|| CAR : owns
+ classDef default fill:#f9f,stroke-width:4px
+ classDef foo stroke:#f00,stroke-dasharray: 5 5`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("erd");
+
+ const entityContainers = graph.nodes
+ .flat()
+ .filter(
+ (node: any) =>
+ node.type === "rectangle" && String(node.id).startsWith("entity-")
+ );
+ const personContainer = entityContainers.find((node: any) =>
+ String(node.id).startsWith("entity-PERSON-")
+ );
+
+ expect(personContainer).toMatchObject({
+ bgColor: "#f9f",
+ strokeColor: "#f00",
+ strokeWidth: 4,
+ strokeStyle: "dashed",
+ });
+
+ const personLines = graph.lines.filter(
+ (line) => line.metadata?.entityId === personContainer?.id
+ );
+
+ expect(personLines.length).toBeGreaterThan(0);
+ personLines.forEach((line) => {
+ expect(line).toMatchObject({
+ strokeColor: "#f00",
+ strokeWidth: 4,
+ strokeStyle: "dashed",
+ });
+ });
+ });
+
+ it("preserves styled ERD header and attribute text colors", async () => {
+ const definition = `erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ ORDER ||--|{ LINE-ITEM : contains
+ PRODUCT ||--o{ LINE-ITEM : included-in
+ CATEGORY ||--o{ PRODUCT : categorizes
+
+ CUSTOMER {
+ int customer_id PK
+ string first_name
+ string last_name
+ string email
+ string phone
+ }
+ ORDER {
+ int order_id PK
+ int customer_id FK
+ datetime order_date
+ string status
+ decimal total_amount
+ }
+ LINE-ITEM {
+ int line_item_id PK
+ int order_id FK
+ int product_id FK
+ int quantity
+ decimal unit_price
+ }
+ PRODUCT {
+ int product_id PK
+ string name
+ string sku
+ decimal price
+ int stock_quantity
+ }
+ CATEGORY {
+ int category_id PK
+ string name
+ string description
+ }
+
+ style CUSTOMER fill:#2d3436,stroke:#00cec9,stroke-width:2px,color:#00cec9
+ style ORDER fill:#2d3436,stroke:#0984e3,stroke-width:2px,color:#0984e3
+ style LINE-ITEM fill:#2d3436,stroke:#6c5ce7,stroke-width:2px,color:#6c5ce7
+ style PRODUCT fill:#2d3436,stroke:#e17055,stroke-width:2px,color:#e17055
+ style CATEGORY fill:#2d3436,stroke:#fdcb6e,stroke-width:2px,color:#fdcb6e`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("erd");
+
+ const result = graphToExcalidraw(graph);
+
+ expect(
+ result.elements.find(
+ (element: any) =>
+ element.type === "rectangle" && element.label?.text === "CUSTOMER"
+ )
+ ).toMatchObject({
+ backgroundColor: "#2d3436",
+ strokeColor: "#00cec9",
+ label: {
+ strokeColor: "#00cec9",
+ },
+ });
+
+ expect(
+ result.elements.find(
+ (element: any) =>
+ element.type === "text" && element.text === "customer_id"
+ )
+ ).toMatchObject({
+ strokeColor: "#00cec9",
+ });
+
+ expect(
+ result.elements.find(
+ (element: any) =>
+ element.type === "text" && element.text === "order_date"
+ )
+ ).toMatchObject({
+ strokeColor: "#0984e3",
+ });
+
+ expect(
+ result.elements.find(
+ (element: any) =>
+ element.type === "text" && element.text === "unit_price"
+ )
+ ).toMatchObject({
+ strokeColor: "#6c5ce7",
+ });
+
+ expect(
+ result.elements.find(
+ (element: any) =>
+ element.type === "text" && element.text === "description"
+ )
+ ).toMatchObject({
+ strokeColor: "#fdcb6e",
+ });
+ });
+
+ it("maps ERD cardinalities to supported arrowheads and centers relationship labels", async () => {
+ const definition = `erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ ORDER ||--|{ LINE-ITEM : contains
+ CUSTOMER }o..o{ DELIVERY-ADDRESS : uses`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("erd");
+
+ const result = graphToExcalidraw(graph);
+ const arrows = result.elements.filter(
+ (element: any) => element.type === "arrow"
+ );
+
+ expect(arrows).toHaveLength(3);
+ expect(
+ arrows.map((arrow: any) => ({
+ label: arrow.label?.text,
+ textAlign: arrow.label?.textAlign,
+ startArrowhead: arrow.startArrowhead,
+ endArrowhead: arrow.endArrowhead,
+ strokeStyle: arrow.strokeStyle,
+ }))
+ ).toEqual([
+ {
+ label: "places",
+ textAlign: "center",
+ startArrowhead: "cardinality_exactly_one",
+ endArrowhead: "cardinality_zero_or_many",
+ strokeStyle: "solid",
+ },
+ {
+ label: "contains",
+ textAlign: "center",
+ startArrowhead: "cardinality_exactly_one",
+ endArrowhead: "cardinality_one_or_many",
+ strokeStyle: "solid",
+ },
+ {
+ label: "uses",
+ textAlign: "center",
+ startArrowhead: "cardinality_zero_or_many",
+ endArrowhead: "cardinality_zero_or_many",
+ strokeStyle: "dashed",
+ },
+ ]);
+ });
+
+ it("parses ERD self relationships by merging Mermaid's cyclic edge segments", async () => {
+ const definition = `erDiagram
+ CATEGORY ||--o{ CATEGORY : parent_of
+ CATEGORY {
+ integer id PK
+ integer parent_id FK
+ string display_name
+ }`;
+
+ const graph = await parseMermaid(definition);
+ expect(graph.type).toBe("erd");
+
+ const result = graphToExcalidraw(graph);
+ const arrows = result.elements.filter(
+ (element: any) => element.type === "arrow"
+ );
+
+ expect(arrows).toHaveLength(1);
+ expect(arrows[0]).toMatchObject({
+ startArrowhead: "cardinality_exactly_one",
+ endArrowhead: "cardinality_zero_or_many",
+ label: {
+ text: "parent_of",
+ textAlign: "center",
+ },
+ });
+ expect(arrows[0].points.length).toBeGreaterThan(2);
+ expect(hasConsecutiveDuplicatePoints(arrows[0].points)).toBe(false);
+ });
+
+ it("parses every playground example without malformed output colors", async () => {
+ for (const testcase of PLAYGROUND_TESTCASES) {
+ const graph = await parseMermaid(testcase.definition);
+ const expectedType =
+ testcase.type === "unsupported" ? "graphImage" : testcase.type;
+
+ expect(graph.type).toBe(expectedType);
+
+ const result = graphToExcalidraw(graph);
+ expect(result.elements.length).toBeGreaterThan(0);
+
+ for (const entry of collectColorEntries(result.elements)) {
+ if (!isValidCSSColor(entry.value)) {
+ throw new Error(
+ `${testcase.type}:${testcase.name} produced invalid color ${entry.value} at ${entry.path}`
+ );
+ }
+ }
+ }
+ }, 20000);
+});
diff --git a/tests/sequence.test.ts b/tests/sequence.test.ts
new file mode 100644
index 00000000..181b2a6a
--- /dev/null
+++ b/tests/sequence.test.ts
@@ -0,0 +1,171 @@
+import type { Diagram } from "mermaid/dist/Diagram.js";
+import { parseMermaidSequenceDiagram } from "../src/parser/sequence.js";
+
+const SVG_NS = "http://www.w3.org/2000/svg";
+
+const createSvgElement = (
+ tagName: string,
+ attrs: Record = {}
+) => {
+ const el = document.createElementNS(SVG_NS, tagName);
+ Object.entries(attrs).forEach(([key, value]) => {
+ el.setAttribute(key, String(value));
+ });
+ return el;
+};
+
+describe("Sequence parser", () => {
+ const originalGetBBox = SVGElement.prototype.getBBox;
+
+ beforeAll(() => {
+ Object.defineProperty(SVGElement.prototype, "getBBox", {
+ configurable: true,
+ value: function getBBox() {
+ const tag = this.tagName.toLowerCase();
+ const num = (key: string, fallback = 0) =>
+ Number(this.getAttribute(key) ?? fallback);
+
+ if (tag === "rect") {
+ return {
+ x: num("x"),
+ y: num("y"),
+ width: num("width"),
+ height: num("height"),
+ };
+ }
+
+ if (tag === "circle") {
+ const r = num("r");
+ const cx = num("cx");
+ const cy = num("cy");
+ return {
+ x: cx - r,
+ y: cy - r,
+ width: r * 2,
+ height: r * 2,
+ };
+ }
+
+ if (tag === "text") {
+ const text = this.textContent ?? "";
+ return {
+ x: num("x"),
+ y: num("y"),
+ width: Math.max(text.length * 8, 1),
+ height: 16,
+ };
+ }
+
+ return { x: 0, y: 0, width: 0, height: 0 };
+ },
+ });
+ });
+
+ afterAll(() => {
+ Object.defineProperty(SVGElement.prototype, "getBBox", {
+ configurable: true,
+ value: originalGetBBox,
+ });
+ });
+
+ it("parses actor lifelines when actor lines are wrapped in groups", () => {
+ const container = document.createElement("div");
+ const svg = createSvgElement("svg");
+ container.appendChild(svg);
+
+ const actorNames = [
+ { name: "Alice", x: 120 },
+ { name: "Bob", x: 300 },
+ ];
+
+ actorNames.forEach(({ name, x }) => {
+ const lineGroup = createSvgElement("g");
+ lineGroup.appendChild(
+ createSvgElement("line", {
+ class: "actor-line 200",
+ name,
+ x1: x,
+ y1: 80,
+ x2: x,
+ y2: 280,
+ })
+ );
+ svg.appendChild(lineGroup);
+
+ const actorTop = createSvgElement("g", {
+ class: "actor-man actor-top",
+ name,
+ });
+ actorTop.appendChild(
+ createSvgElement("circle", { cx: x, cy: 30, r: 12 })
+ );
+ svg.appendChild(actorTop);
+
+ const actorBottom = createSvgElement("g", {
+ class: "actor-man actor-bottom",
+ name,
+ });
+ actorBottom.appendChild(
+ createSvgElement("circle", { cx: x, cy: 320, r: 12 })
+ );
+ svg.appendChild(actorBottom);
+ });
+
+ svg.appendChild(
+ createSvgElement("line", {
+ class: "messageLine0",
+ x1: 120,
+ y1: 140,
+ x2: 300,
+ y2: 140,
+ })
+ );
+ svg.appendChild(
+ createSvgElement("line", {
+ class: "messageLine0",
+ x1: 300,
+ y1: 180,
+ x2: 120,
+ y2: 180,
+ })
+ );
+
+ const actorData = new Map([
+ [
+ "Alice",
+ { name: "Alice", description: "Alice", type: "actor" as const },
+ ],
+ ["Bob", { name: "Bob", description: "Bob", type: "actor" as const }],
+ ]);
+ const messages = [
+ { type: 0, from: "Alice", to: "Bob", message: "Hi Bob", wrap: true },
+ { type: 0, from: "Bob", to: "Alice", message: "Hi Alice", wrap: true },
+ ];
+
+ const diagram = {
+ text: `sequenceDiagram
+ actor Alice
+ actor Bob
+ Alice->>Bob: Hi Bob
+ Bob->>Alice: Hi Alice`,
+ db: {
+ getBoxes: () => [],
+ getActors: () => actorData,
+ getMessages: () => messages,
+ },
+ parser: {
+ parse: vi.fn(),
+ },
+ } as unknown as Diagram;
+
+ const result = parseMermaidSequenceDiagram(diagram, container);
+
+ expect(result.type).toBe("sequence");
+ expect(result.lines).toHaveLength(2);
+ expect(result.arrows).toHaveLength(2);
+ expect(result.arrows[0].start?.type).toBe("ellipse");
+ expect(result.arrows[0].end?.type).toBe("ellipse");
+ expect(result.arrows[0].start?.id).toBe("Alice-top-0");
+ expect(result.arrows[0].end?.id).toBe("Bob-top-0");
+ });
+});
diff --git a/tests/utils.test.ts b/tests/utils.test.ts
index 43c491ae..5c895912 100644
--- a/tests/utils.test.ts
+++ b/tests/utils.test.ts
@@ -2,6 +2,7 @@ import {
getTransformAttr,
entityCodesToText,
computeEdgePositions,
+ dedupeConsecutivePoints,
} from "../src/utils.js";
describe("Test Utils", () => {
@@ -108,4 +109,73 @@ describe("Test Utils", () => {
]);
});
});
+
+ describe("Test dedupeConsecutivePoints", () => {
+ it("should filter out consecutive duplicate tuple points", () => {
+ expect(
+ dedupeConsecutivePoints([
+ [0, 0],
+ [0, 0],
+ [10, 5],
+ [10, 5],
+ [20, 10],
+ ])
+ ).toEqual([
+ [0, 0],
+ [10, 5],
+ [20, 10],
+ ]);
+ });
+
+ it("should preserve non-consecutive repeated tuple points", () => {
+ expect(
+ dedupeConsecutivePoints([
+ [0, 0],
+ [10, 5],
+ [0, 0],
+ ])
+ ).toEqual([
+ [0, 0],
+ [10, 5],
+ [0, 0],
+ ]);
+ });
+
+ it("should filter out very close consecutive points within the threshold", () => {
+ expect(
+ dedupeConsecutivePoints([
+ [0, 0],
+ [-11.143636363744733, 40.5],
+ [-11.143636363744733, 80.5],
+ [-11.143636363744733, 80.60000000149012],
+ [-11.143636363744733, 131.10000000149012],
+ [28.841774120706717, 181.60000000149012],
+ [28.920953152548876, 181.60000000149012],
+ [68.90636363700033, 131.10000000149012],
+ ])
+ ).toEqual([
+ [0, 0],
+ [-11.143636363744733, 40.5],
+ [-11.143636363744733, 80.5],
+ [-11.143636363744733, 131.10000000149012],
+ [28.841774120706717, 181.60000000149012],
+ [68.90636363700033, 131.10000000149012],
+ ]);
+ });
+
+ it("should compare against the last kept point when applying the threshold", () => {
+ expect(
+ dedupeConsecutivePoints([
+ [0, 0],
+ [0.4, 0],
+ [0.8, 0],
+ [1.5, 0],
+ ])
+ ).toEqual([
+ [0, 0],
+ [0.8, 0],
+ [1.5, 0],
+ ]);
+ });
+ });
});
diff --git a/tsconfig.common.json b/tsconfig.common.json
index c1834e31..c925d2e2 100644
--- a/tsconfig.common.json
+++ b/tsconfig.common.json
@@ -8,7 +8,8 @@
"allowSyntheticDefaultImports": true,
"moduleResolution": "NodeNext",
"forceConsistentCasingInFileNames": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "sourceMap": true
},
"exclude": ["node_modules", "**/__tests__/*"]
}
diff --git a/yarn.lock b/yarn.lock
index d8daf339..76c99866 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -15,6 +15,19 @@
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
+"@antfu/install-pkg@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz#78fa036be1a6081b5a77a5cf59f50c7752b6ba26"
+ integrity sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==
+ dependencies:
+ package-manager-detector "^1.3.0"
+ tinyexec "^1.0.1"
+
+"@antfu/utils@^9.2.0":
+ version "9.3.0"
+ resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-9.3.0.tgz#e05e277f788ac3bec771f57a49fb64546bb32374"
+ integrity sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==
+
"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658"
@@ -878,16 +891,87 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-"@braintree/sanitize-url@^6.0.1":
- version "6.0.4"
- resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783"
- integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==
-
-"@braintree/sanitize-url@^6.0.2":
+"@braintree/sanitize-url@6.0.2", "@braintree/sanitize-url@^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f"
integrity sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==
+"@braintree/sanitize-url@^7.1.1":
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz#15e19737d946559289b915e5dad3b4c28407735e"
+ integrity sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==
+
+"@chevrotain/cst-dts-gen@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz#5e0863cc57dc45e204ccfee6303225d15d9d4783"
+ integrity sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==
+ dependencies:
+ "@chevrotain/gast" "11.0.3"
+ "@chevrotain/types" "11.0.3"
+ lodash-es "4.17.21"
+
+"@chevrotain/gast@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/gast/-/gast-11.0.3.tgz#e84d8880323fe8cbe792ef69ce3ffd43a936e818"
+ integrity sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==
+ dependencies:
+ "@chevrotain/types" "11.0.3"
+ lodash-es "4.17.21"
+
+"@chevrotain/regexp-to-ast@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz#11429a81c74a8e6a829271ce02fc66166d56dcdb"
+ integrity sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==
+
+"@chevrotain/types@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/types/-/types-11.0.3.tgz#f8a03914f7b937f594f56eb89312b3b8f1c91848"
+ integrity sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==
+
+"@chevrotain/utils@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-11.0.3.tgz#e39999307b102cff3645ec4f5b3665f5297a2224"
+ integrity sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==
+
+"@codemirror/commands@^6.0.0":
+ version "6.10.2"
+ resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.10.2.tgz#338bf53ab146de7bb26da4a1d32c6a6ff4d36b39"
+ integrity sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==
+ dependencies:
+ "@codemirror/language" "^6.0.0"
+ "@codemirror/state" "^6.4.0"
+ "@codemirror/view" "^6.27.0"
+ "@lezer/common" "^1.1.0"
+
+"@codemirror/language@^6.0.0":
+ version "6.12.2"
+ resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.12.2.tgz#7db5a46757411cf251e8f450474c05710c27d42c"
+ integrity sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==
+ dependencies:
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.23.0"
+ "@lezer/common" "^1.5.0"
+ "@lezer/highlight" "^1.0.0"
+ "@lezer/lr" "^1.0.0"
+ style-mod "^4.0.0"
+
+"@codemirror/state@^6.0.0", "@codemirror/state@^6.4.0", "@codemirror/state@^6.5.0":
+ version "6.5.4"
+ resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.5.4.tgz#f5be4b8c0d2310180d5f15a9f641c21ca69faf19"
+ integrity sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==
+ dependencies:
+ "@marijn/find-cluster-break" "^1.0.0"
+
+"@codemirror/view@^6.0.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0":
+ version "6.39.17"
+ resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.39.17.tgz#7ea54f4e96319ecbcb0de6b39fd0c6479be84d7d"
+ integrity sha512-Aim4lFqhbijnchl83RLfABWueSGs1oUCSv0mru91QdhpXQeNKprIdRO9LWA4cYkJvuYTKGJN7++9MXx8XW43ag==
+ dependencies:
+ "@codemirror/state" "^6.5.0"
+ crelt "^1.0.6"
+ style-mod "^4.1.0"
+ w3c-keyname "^2.2.4"
+
"@esbuild/aix-ppc64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
@@ -1035,21 +1119,128 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6"
integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==
+"@excalidraw/common@0.18.0-816c81c":
+ version "0.18.0-816c81c"
+ resolved "https://registry.yarnpkg.com/@excalidraw/common/-/common-0.18.0-816c81c.tgz#8b4ea61f6a866b1c04c4ee35994b3ca2b68b0739"
+ integrity sha512-trDWhlYruUquUE15qebwjPqn2ov4A+gnMGUa/i4zaimXJlWLBSgwWuSn5PS9baE+xD/Qmka/9g9A4yan/AO9Qw==
+ dependencies:
+ tinycolor2 "1.6.0"
+
+"@excalidraw/element@0.18.0-816c81c":
+ version "0.18.0-816c81c"
+ resolved "https://registry.yarnpkg.com/@excalidraw/element/-/element-0.18.0-816c81c.tgz#fa4038a97877be9d7524f813aba2760f31ec355f"
+ integrity sha512-krvXRa5lvpTLWeL0Hrh6a0ptb0drHTveq8mPgElDfF23jJ98AID4//xxq3A8pMNh5+efn68XkTEAPwX28FkJYg==
+ dependencies:
+ "@excalidraw/common" "0.18.0-816c81c"
+ "@excalidraw/math" "0.18.0-816c81c"
+
"@excalidraw/eslint-config@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@excalidraw/eslint-config/-/eslint-config-1.0.3.tgz#2122ef7413ae77874ae9848ce0f1c6b3f0d8bbbd"
integrity sha512-GemHNF5Z6ga0BWBSX7GJaNBUchLu6RwTcAB84eX1MeckRNhNasAsPCdelDlFalz27iS4RuYEQh0bPE8SRxJgbQ==
-"@excalidraw/excalidraw@0.17.1-7381-cdf6d3e":
- version "0.17.1-7381-cdf6d3e"
- resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.17.1-7381-cdf6d3e.tgz#ab01bc27639a7828edf5ebaba288969713477177"
- integrity sha512-gRjaw9HQ5qzlhqvtnUK1ujakZnp5l4zgLmnskxG3WzmVXwEd+lK8etDEdXjAKmw7cmG+3e0jO0wFHKNqBxoKNA==
+"@excalidraw/excalidraw@^0.18.0-816c81c":
+ version "0.18.0-816c81c"
+ resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.18.0-816c81c.tgz#dc45daad65b2288e4e2be3583055a836dc098749"
+ integrity sha512-Evl1W5NOZ9yo6JrsUQWax02oBlNT2XhI5TIDFOXDltmWNu1kYAVKdRyYFB1gLc132mEPUP3AijLFaD5PCRtDzg==
+ dependencies:
+ "@braintree/sanitize-url" "6.0.2"
+ "@codemirror/commands" "^6.0.0"
+ "@codemirror/language" "^6.0.0"
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.0.0"
+ "@excalidraw/common" "0.18.0-816c81c"
+ "@excalidraw/element" "0.18.0-816c81c"
+ "@excalidraw/laser-pointer" "1.3.1"
+ "@excalidraw/math" "0.18.0-816c81c"
+ "@excalidraw/mermaid-to-excalidraw" "2.1.0"
+ "@excalidraw/random-username" "1.1.0"
+ "@lezer/highlight" "^1.0.0"
+ browser-fs-access "0.38.0"
+ canvas-roundrect-polyfill "0.0.1"
+ clsx "1.1.1"
+ cross-env "7.0.3"
+ es6-promise-pool "2.5.0"
+ fractional-indexing "3.2.0"
+ fuzzy "0.1.3"
+ image-blob-reduce "3.0.1"
+ jotai "2.11.0"
+ jotai-scope "0.7.2"
+ lodash.debounce "4.0.8"
+ lodash.throttle "4.1.1"
+ nanoid "3.3.3"
+ pako "2.0.3"
+ perfect-freehand "1.2.0"
+ pica "7.1.1"
+ png-chunk-text "1.0.0"
+ png-chunks-encode "1.0.0"
+ png-chunks-extract "1.0.0"
+ points-on-curve "1.0.1"
+ pwacompat "2.0.17"
+ radix-ui "1.4.3"
+ roughjs "4.6.4"
+ sass "1.51.0"
+ tunnel-rat "0.1.2"
+
+"@excalidraw/laser-pointer@1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@excalidraw/laser-pointer/-/laser-pointer-1.3.1.tgz#7c40836598e8e6ad91f01057883ed8b88fb9266c"
+ integrity sha512-psA1z1N2qeAfsORdXc9JmD2y4CmDwmuMRxnNdJHZexIcPwaNEyIpNcelw+QkL9rz9tosaN9krXuKaRqYpRAR6g==
"@excalidraw/markdown-to-text@0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@excalidraw/markdown-to-text/-/markdown-to-text-0.1.2.tgz#1703705e7da608cf478f17bfe96fb295f55a23eb"
integrity sha512-1nDXBNAojfi3oSFwJswKREkFm5wrSjqay81QlyRv2pkITG/XYB5v+oChENVBQLcxQwX4IUATWvXM5BcaNhPiIg==
+"@excalidraw/math@0.18.0-816c81c":
+ version "0.18.0-816c81c"
+ resolved "https://registry.yarnpkg.com/@excalidraw/math/-/math-0.18.0-816c81c.tgz#b6233894ef02c40250b2828bdcdcee188d448fa7"
+ integrity sha512-MKZrDCTaIOoneVoV7MrPkiSWLLZW0GltoOYCmQNx9LMpJiFidaCKE3WFMcz6x2l8E0Ytz0NMJJuc9+WZg+HcPg==
+ dependencies:
+ "@excalidraw/common" "0.18.0-816c81c"
+
+"@excalidraw/mermaid-to-excalidraw@2.1.0":
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/@excalidraw/mermaid-to-excalidraw/-/mermaid-to-excalidraw-2.1.0.tgz#a5b9cf87c3185558cda7f9687d87b9937f452358"
+ integrity sha512-RMd+c2b7WzzUjhERMpKwp8PhF2/XlHDjr/zK+Gxfp8K9sVlafPYJ5OEa/GkN6edi2rBUXRfW+41WdO6L56b6Kw==
+ dependencies:
+ "@excalidraw/markdown-to-text" "0.1.2"
+ "@mermaid-js/parser" "^0.6.3"
+ mermaid "^11.12.1"
+ nanoid "4.0.2"
+
+"@excalidraw/random-username@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@excalidraw/random-username/-/random-username-1.1.0.tgz#6f388d6a9708cf655b8c9c6aa3fa569ee71ecf0f"
+ integrity sha512-nULYsQxkWHnbmHvcs+efMkJ4/9TtvNyFeLyHdeGxW0zHs6P+jYVqcRff9A6Vq9w9JXeDRnRh2VKvTtS19GW2qA==
+
+"@floating-ui/core@^1.7.5":
+ version "1.7.5"
+ resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.5.tgz#d4af157a03330af5a60e69da7a4692507ada0622"
+ integrity sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==
+ dependencies:
+ "@floating-ui/utils" "^0.2.11"
+
+"@floating-ui/dom@^1.7.6":
+ version "1.7.6"
+ resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.6.tgz#f915bba5abbb177e1f227cacee1b4d0634b187bf"
+ integrity sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==
+ dependencies:
+ "@floating-ui/core" "^1.7.5"
+ "@floating-ui/utils" "^0.2.11"
+
+"@floating-ui/react-dom@^2.0.0":
+ version "2.1.8"
+ resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.8.tgz#5fb5a20d10aafb9505f38c24f38d00c8e1598893"
+ integrity sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==
+ dependencies:
+ "@floating-ui/dom" "^1.7.6"
+
+"@floating-ui/utils@^0.2.11":
+ version "0.2.11"
+ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.11.tgz#a269e055e40e2f45873bae9d1a2fdccbd314ea3f"
+ integrity sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==
+
"@humanwhocodes/config-array@^0.11.10":
version "0.11.10"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2"
@@ -1069,6 +1260,25 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
+"@iconify/types@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57"
+ integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==
+
+"@iconify/utils@^3.0.1":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@iconify/utils/-/utils-3.0.2.tgz#9599607f20690cd3e7a5d2d459af0eb81a89dc2b"
+ integrity sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==
+ dependencies:
+ "@antfu/install-pkg" "^1.1.0"
+ "@antfu/utils" "^9.2.0"
+ "@iconify/types" "^2.0.0"
+ debug "^4.4.1"
+ globals "^15.15.0"
+ kolorist "^1.8.0"
+ local-pkg "^1.1.1"
+ mlly "^1.7.4"
+
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@@ -1157,6 +1367,37 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@lezer/common@^1.0.0", "@lezer/common@^1.1.0", "@lezer/common@^1.3.0", "@lezer/common@^1.5.0":
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.5.1.tgz#6e8c114ff5d36a41148e146a253734d3bb8807d3"
+ integrity sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==
+
+"@lezer/highlight@^1.0.0":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.3.tgz#a20f324b71148a2ea9ba6ff42e58bbfaec702857"
+ integrity sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==
+ dependencies:
+ "@lezer/common" "^1.3.0"
+
+"@lezer/lr@^1.0.0":
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.8.tgz#333de9bc9346057323ff09beb4cda47ccc38a498"
+ integrity sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==
+ dependencies:
+ "@lezer/common" "^1.0.0"
+
+"@marijn/find-cluster-break@^1.0.0":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz#775374306116d51c0c500b8c4face0f9a04752d8"
+ integrity sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==
+
+"@mermaid-js/parser@^0.6.3":
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/@mermaid-js/parser/-/parser-0.6.3.tgz#3ce92dad2c5d696d29e11e21109c66a7886c824e"
+ integrity sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==
+ dependencies:
+ langium "3.3.1"
+
"@nicolo-ribaudo/semver-v6@^6.3.3":
version "6.3.3"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz#ea6d23ade78a325f7a52750aab1526b02b628c29"
@@ -1188,6 +1429,671 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+"@radix-ui/number@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.1.tgz#7b2c9225fbf1b126539551f5985769d0048d9090"
+ integrity sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==
+
+"@radix-ui/primitive@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.3.tgz#e2dbc13bdc5e4168f4334f75832d7bdd3e2de5ba"
+ integrity sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==
+
+"@radix-ui/react-accessible-icon@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz#3b1629ce0c5ce0f791a21e28cfa6a1ffb82e2029"
+ integrity sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==
+ dependencies:
+ "@radix-ui/react-visually-hidden" "1.2.3"
+
+"@radix-ui/react-accordion@1.2.12":
+ version "1.2.12"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz#1fd70d4ef36018012b9e03324ff186de7a29c13f"
+ integrity sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collapsible" "1.1.12"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-alert-dialog@1.1.15":
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz#fa751d0fdd9aa2a90961c9901dba18e638dd4b41"
+ integrity sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-dialog" "1.1.15"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+
+"@radix-ui/react-arrow@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz#e14a2657c81d961598c5e72b73dd6098acc04f09"
+ integrity sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.3"
+
+"@radix-ui/react-aspect-ratio@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz#95d0adcdddd0d40c5dd2ae07c8608b4f0b983f53"
+ integrity sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.3"
+
+"@radix-ui/react-avatar@1.1.10":
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz#c58a8800ef3d3ee783b3168fee7c76f6534bfd93"
+ integrity sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==
+ dependencies:
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-is-hydrated" "0.1.0"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-checkbox@1.3.3":
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz#db45ca8a6d5c056a92f74edbb564acee05318b79"
+ integrity sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-previous" "1.1.1"
+ "@radix-ui/react-use-size" "1.1.1"
+
+"@radix-ui/react-collapsible@1.1.12":
+ version "1.1.12"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz#e2cc69a4490a2920f97c3c3150b0bf21281e3c49"
+ integrity sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-collection@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.7.tgz#d05c25ca9ac4695cc19ba91f42f686e3ea2d9aec"
+ integrity sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+
+"@radix-ui/react-compose-refs@1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30"
+ integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
+
+"@radix-ui/react-context-menu@2.2.16":
+ version "2.2.16"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz#e7bf94a457b68af08f24ad696949144530faab50"
+ integrity sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-menu" "2.1.16"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-context@1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36"
+ integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
+
+"@radix-ui/react-dialog@1.1.15":
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz#1de3d7a7e9a17a9874d29c07f5940a18a119b632"
+ integrity sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-focus-guards" "1.1.3"
+ "@radix-ui/react-focus-scope" "1.1.7"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ aria-hidden "^1.2.4"
+ react-remove-scroll "^2.6.3"
+
+"@radix-ui/react-direction@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz#39e5a5769e676c753204b792fbe6cf508e550a14"
+ integrity sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==
+
+"@radix-ui/react-dismissable-layer@1.1.11":
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz#e33ab6f6bdaa00f8f7327c408d9f631376b88b37"
+ integrity sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-escape-keydown" "1.1.1"
+
+"@radix-ui/react-dropdown-menu@2.1.16":
+ version "2.1.16"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz#5ee045c62bad8122347981c479d92b1ff24c7254"
+ integrity sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-menu" "2.1.16"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-focus-guards@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz#2a5669e464ad5fde9f86d22f7fdc17781a4dfa7f"
+ integrity sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==
+
+"@radix-ui/react-focus-scope@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz#dfe76fc103537d80bf42723a183773fd07bfb58d"
+ integrity sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+
+"@radix-ui/react-form@0.1.8":
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-form/-/react-form-0.1.8.tgz#daec0fde305a70edf1a97b932b5e02a4cbf5b68e"
+ integrity sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-label" "2.1.7"
+ "@radix-ui/react-primitive" "2.1.3"
+
+"@radix-ui/react-hover-card@1.1.15":
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz#9bc7ed55c37a9032acdfcc7cfa5c73b117cffe5e"
+ integrity sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-id@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.1.tgz#1404002e79a03fe062b7e3864aa01e24bd1471f7"
+ integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==
+ dependencies:
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-label@2.1.7":
+ version "2.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-2.1.7.tgz#ad959ff9c6e4968d533329eb95696e1ba8ad72ab"
+ integrity sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.3"
+
+"@radix-ui/react-menu@2.1.16":
+ version "2.1.16"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.16.tgz#528a5a973c3a7413d3d49eb9ccd229aa52402911"
+ integrity sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-focus-guards" "1.1.3"
+ "@radix-ui/react-focus-scope" "1.1.7"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ aria-hidden "^1.2.4"
+ react-remove-scroll "^2.6.3"
+
+"@radix-ui/react-menubar@1.1.16":
+ version "1.1.16"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz#5edf7ea2ff7aa7e3ba896b35cf577f122160121c"
+ integrity sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-menu" "2.1.16"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-navigation-menu@1.2.14":
+ version "1.2.14"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz#4e6d1172be3c89752e564f8721706f78574ad7dd"
+ integrity sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+ "@radix-ui/react-use-previous" "1.1.1"
+ "@radix-ui/react-visually-hidden" "1.2.3"
+
+"@radix-ui/react-one-time-password-field@0.1.8":
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz#edb7476d29478477ffc837f7deacec3a1ae08a24"
+ integrity sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==
+ dependencies:
+ "@radix-ui/number" "1.1.1"
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-effect-event" "0.0.2"
+ "@radix-ui/react-use-is-hydrated" "0.1.0"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-password-toggle-field@0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz#3d47de91c0f8e79d697cefde2ef8146816712031"
+ integrity sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-effect-event" "0.0.2"
+ "@radix-ui/react-use-is-hydrated" "0.1.0"
+
+"@radix-ui/react-popover@1.1.15":
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.1.15.tgz#9c852f93990a687ebdc949b2c3de1f37cdc4c5d5"
+ integrity sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-focus-guards" "1.1.3"
+ "@radix-ui/react-focus-scope" "1.1.7"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ aria-hidden "^1.2.4"
+ react-remove-scroll "^2.6.3"
+
+"@radix-ui/react-popper@1.2.8":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.8.tgz#a79f39cdd2b09ab9fb50bf95250918422c4d9602"
+ integrity sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==
+ dependencies:
+ "@floating-ui/react-dom" "^2.0.0"
+ "@radix-ui/react-arrow" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+ "@radix-ui/react-use-rect" "1.1.1"
+ "@radix-ui/react-use-size" "1.1.1"
+ "@radix-ui/rect" "1.1.1"
+
+"@radix-ui/react-portal@1.1.9":
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz#14c3649fe48ec474ac51ed9f2b9f5da4d91c4472"
+ integrity sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-presence@1.1.5":
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.5.tgz#5d8f28ac316c32f078afce2996839250c10693db"
+ integrity sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-primitive@2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz#db9b8bcff49e01be510ad79893fb0e4cda50f1bc"
+ integrity sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==
+ dependencies:
+ "@radix-ui/react-slot" "1.2.3"
+
+"@radix-ui/react-progress@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.1.7.tgz#a2b76398b3f24b6bd5e37f112b1e30fbedd4f38e"
+ integrity sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==
+ dependencies:
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+
+"@radix-ui/react-radio-group@1.3.8":
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz#93f102b5b948d602c2f2adb1bc5c347cbaf64bd9"
+ integrity sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-previous" "1.1.1"
+ "@radix-ui/react-use-size" "1.1.1"
+
+"@radix-ui/react-roving-focus@1.1.11":
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz#ef54384b7361afc6480dcf9907ef2fedb5080fd9"
+ integrity sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-scroll-area@1.2.10":
+ version "1.2.10"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz#e4fd3b4a79bb77bec1a52f0c8f26d8f3f1ca4b22"
+ integrity sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==
+ dependencies:
+ "@radix-ui/number" "1.1.1"
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-select@2.2.6":
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.2.6.tgz#022cf8dab16bf05d0d1b4df9e53e4bea1b744fd9"
+ integrity sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==
+ dependencies:
+ "@radix-ui/number" "1.1.1"
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-focus-guards" "1.1.3"
+ "@radix-ui/react-focus-scope" "1.1.7"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+ "@radix-ui/react-use-previous" "1.1.1"
+ "@radix-ui/react-visually-hidden" "1.2.3"
+ aria-hidden "^1.2.4"
+ react-remove-scroll "^2.6.3"
+
+"@radix-ui/react-separator@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.7.tgz#a18bd7fd07c10fda1bba14f2a3032e7b1a2b3470"
+ integrity sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.3"
+
+"@radix-ui/react-slider@1.3.6":
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-slider/-/react-slider-1.3.6.tgz#409453110b8f34ca00972750b80cd792f0b23a8c"
+ integrity sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==
+ dependencies:
+ "@radix-ui/number" "1.1.1"
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+ "@radix-ui/react-use-previous" "1.1.1"
+ "@radix-ui/react-use-size" "1.1.1"
+
+"@radix-ui/react-slot@1.2.3":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz#502d6e354fc847d4169c3bc5f189de777f68cfe1"
+ integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+
+"@radix-ui/react-switch@1.2.6":
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.2.6.tgz#ff79acb831f0d5ea9216cfcc5b939912571358e3"
+ integrity sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-previous" "1.1.1"
+ "@radix-ui/react-use-size" "1.1.1"
+
+"@radix-ui/react-tabs@1.1.13":
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz#3537ce379d7e7ff4eeb6b67a0973e139c2ac1f15"
+ integrity sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-toast@1.2.15":
+ version "1.2.15"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-toast/-/react-toast-1.2.15.tgz#746cf9a81297ddbfba214e5c81245ea3f706f876"
+ integrity sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+ "@radix-ui/react-visually-hidden" "1.2.3"
+
+"@radix-ui/react-toggle-group@1.1.11":
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz#e513d6ffdb07509b400ab5b26f2523747c0d51c1"
+ integrity sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-toggle" "1.1.10"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-toggle@1.1.10":
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz#b04ba0f9609599df666fce5b2f38109a197f08cf"
+ integrity sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-toolbar@1.1.11":
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz#2a71f1d91535788f88145d542159e2faaa561db7"
+ integrity sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-separator" "1.1.7"
+ "@radix-ui/react-toggle-group" "1.1.11"
+
+"@radix-ui/react-tooltip@1.2.8":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz#3f50267e25bccfc9e20bb3036bfd9ab4c2c30c2c"
+ integrity sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-visually-hidden" "1.2.3"
+
+"@radix-ui/react-use-callback-ref@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz#62a4dba8b3255fdc5cc7787faeac1c6e4cc58d40"
+ integrity sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==
+
+"@radix-ui/react-use-controllable-state@1.2.2":
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz#905793405de57d61a439f4afebbb17d0645f3190"
+ integrity sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==
+ dependencies:
+ "@radix-ui/react-use-effect-event" "0.0.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-use-effect-event@0.0.2":
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz#090cf30d00a4c7632a15548512e9152217593907"
+ integrity sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==
+ dependencies:
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-use-escape-keydown@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz#b3fed9bbea366a118f40427ac40500aa1423cc29"
+ integrity sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==
+ dependencies:
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+
+"@radix-ui/react-use-is-hydrated@0.1.0":
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz#544da73369517036c77659d7cdd019dc0f5ff9a0"
+ integrity sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==
+ dependencies:
+ use-sync-external-store "^1.5.0"
+
+"@radix-ui/react-use-layout-effect@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz#0c4230a9eed49d4589c967e2d9c0d9d60a23971e"
+ integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==
+
+"@radix-ui/react-use-previous@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz#1a1ad5568973d24051ed0af687766f6c7cb9b5b5"
+ integrity sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==
+
+"@radix-ui/react-use-rect@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz#01443ca8ed071d33023c1113e5173b5ed8769152"
+ integrity sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==
+ dependencies:
+ "@radix-ui/rect" "1.1.1"
+
+"@radix-ui/react-use-size@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz#6de276ffbc389a537ffe4316f5b0f24129405b37"
+ integrity sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==
+ dependencies:
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-visually-hidden@1.2.3":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz#a8c38c8607735dc9f05c32f87ab0f9c2b109efbf"
+ integrity sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.3"
+
+"@radix-ui/rect@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.1.tgz#78244efe12930c56fd255d7923865857c41ac8cb"
+ integrity sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==
+
"@rollup/rollup-android-arm-eabi@4.14.1":
version "4.14.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz#ca0501dd836894216cb9572848c5dde4bfca3bec"
@@ -1349,22 +2255,215 @@
dependencies:
"@swc/counter" "^0.1.3"
-"@types/d3-scale-chromatic@^3.0.0":
+"@types/d3-array@*":
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5"
+ integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==
+
+"@types/d3-axis@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795"
+ integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-brush@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c"
+ integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-chord@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d"
+ integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==
+
+"@types/d3-color@*":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
+ integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
+
+"@types/d3-contour@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231"
+ integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==
+ dependencies:
+ "@types/d3-array" "*"
+ "@types/geojson" "*"
+
+"@types/d3-delaunay@*":
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1"
+ integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==
+
+"@types/d3-dispatch@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz#096efdf55eb97480e3f5621ff9a8da552f0961e7"
+ integrity sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==
+
+"@types/d3-drag@*":
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02"
+ integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-dsv@*":
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17"
+ integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==
+
+"@types/d3-ease@*":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b"
+ integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==
+
+"@types/d3-fetch@*":
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980"
+ integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==
+ dependencies:
+ "@types/d3-dsv" "*"
+
+"@types/d3-force@*":
+ version "3.0.10"
+ resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.10.tgz#6dc8fc6e1f35704f3b057090beeeb7ac674bff1a"
+ integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==
+
+"@types/d3-format@*":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90"
+ integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==
+
+"@types/d3-geo@*":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440"
+ integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==
+ dependencies:
+ "@types/geojson" "*"
+
+"@types/d3-hierarchy@*":
+ version "3.1.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz#6023fb3b2d463229f2d680f9ac4b47466f71f17b"
+ integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==
+
+"@types/d3-interpolate@*":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c"
+ integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==
+ dependencies:
+ "@types/d3-color" "*"
+
+"@types/d3-path@*":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a"
+ integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==
+
+"@types/d3-polygon@*":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c"
+ integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==
+
+"@types/d3-quadtree@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f"
+ integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==
+
+"@types/d3-random@*":
version "3.0.3"
- resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz#fc0db9c10e789c351f4c42d96f31f2e4df8f5644"
- integrity sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==
+ resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb"
+ integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==
-"@types/d3-scale@^4.0.3":
- version "4.0.8"
- resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb"
- integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==
+"@types/d3-scale-chromatic@*":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#dc6d4f9a98376f18ea50bad6c39537f1b5463c39"
+ integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==
+
+"@types/d3-scale@*":
+ version "4.0.9"
+ resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb"
+ integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==
dependencies:
"@types/d3-time" "*"
+"@types/d3-selection@*":
+ version "3.0.11"
+ resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.11.tgz#bd7a45fc0a8c3167a631675e61bc2ca2b058d4a3"
+ integrity sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==
+
+"@types/d3-shape@*":
+ version "3.1.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.7.tgz#2b7b423dc2dfe69c8c93596e673e37443348c555"
+ integrity sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==
+ dependencies:
+ "@types/d3-path" "*"
+
+"@types/d3-time-format@*":
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2"
+ integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==
+
"@types/d3-time@*":
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be"
- integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f"
+ integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==
+
+"@types/d3-timer@*":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70"
+ integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
+
+"@types/d3-transition@*":
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.9.tgz#1136bc57e9ddb3c390dccc9b5ff3b7d2b8d94706"
+ integrity sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-zoom@*":
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b"
+ integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==
+ dependencies:
+ "@types/d3-interpolate" "*"
+ "@types/d3-selection" "*"
+
+"@types/d3@^7.4.3":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2"
+ integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==
+ dependencies:
+ "@types/d3-array" "*"
+ "@types/d3-axis" "*"
+ "@types/d3-brush" "*"
+ "@types/d3-chord" "*"
+ "@types/d3-color" "*"
+ "@types/d3-contour" "*"
+ "@types/d3-delaunay" "*"
+ "@types/d3-dispatch" "*"
+ "@types/d3-drag" "*"
+ "@types/d3-dsv" "*"
+ "@types/d3-ease" "*"
+ "@types/d3-fetch" "*"
+ "@types/d3-force" "*"
+ "@types/d3-format" "*"
+ "@types/d3-geo" "*"
+ "@types/d3-hierarchy" "*"
+ "@types/d3-interpolate" "*"
+ "@types/d3-path" "*"
+ "@types/d3-polygon" "*"
+ "@types/d3-quadtree" "*"
+ "@types/d3-random" "*"
+ "@types/d3-scale" "*"
+ "@types/d3-scale-chromatic" "*"
+ "@types/d3-selection" "*"
+ "@types/d3-shape" "*"
+ "@types/d3-time" "*"
+ "@types/d3-time-format" "*"
+ "@types/d3-timer" "*"
+ "@types/d3-transition" "*"
+ "@types/d3-zoom" "*"
"@types/debug@^4.0.0":
version "4.1.8"
@@ -1378,6 +2477,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
+"@types/geojson@*":
+ version "7946.0.16"
+ resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a"
+ integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==
+
"@types/json-schema@^7.0.9":
version "7.0.12"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
@@ -1390,7 +2494,7 @@
dependencies:
"@types/unist" "*"
-"@types/mermaid@9.2.0":
+"@types/mermaid@^9.2.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@types/mermaid/-/mermaid-9.2.0.tgz#19219b569bc0b6ab5814f82468953bf0bc5e2dec"
integrity sha512-AlvLWYer6u4BkO4QzMkHo0t9RkvVIgqggVZmO+5snUiuX2caTKqtdqygX6GeE1VQa/TnXw9WoH0spcmHtG0inQ==
@@ -1433,6 +2537,11 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a"
integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==
+"@types/trusted-types@^2.0.7":
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
+ integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
+
"@types/unist@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.0.tgz#988ae8af1e5239e89f9fbb1ade4c935f4eeedf9a"
@@ -1612,6 +2721,11 @@ acorn@^8.11.3:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
+acorn@^8.14.0:
+ version "8.14.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
+ integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
+
acorn@^8.9.0:
version "8.10.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
@@ -1681,6 +2795,13 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+aria-hidden@^1.2.4:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.6.tgz#73051c9b088114c795b1ea414e9c0fff874ffc1a"
+ integrity sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==
+ dependencies:
+ tslib "^2.0.0"
+
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
@@ -1752,6 +2873,11 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
+browser-fs-access@0.38.0:
+ version "0.38.0"
+ resolved "https://registry.yarnpkg.com/browser-fs-access/-/browser-fs-access-0.38.0.tgz#9024c5bf3d962287a08d14beebb86cb819cbb838"
+ integrity sha512-JveqW2w6pEZqFEEfMgCszXzYpE89dG+nPsmOdcs741mFFAROeL+iqjGEpR07RI+s0YY0EFr+4KnOoACprJTpOw==
+
browserslist@^4.21.9:
version "4.21.9"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635"
@@ -1777,6 +2903,11 @@ caniuse-lite@^1.0.30001503:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001513.tgz#382fe5fbfb0f7abbaf8c55ca3ac71a0307a752e9"
integrity sha512-pnjGJo7SOOjAGytZZ203Em95MRM8Cr6jhCXNF/FAXTpCTRTECnqQWLpiTRqrFtdYcth8hf4WECUpkezuYsMVww==
+canvas-roundrect-polyfill@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/canvas-roundrect-polyfill/-/canvas-roundrect-polyfill-0.0.1.tgz#70bf107ebe2037f26d839d7f809a26f4a95f5696"
+ integrity sha512-yWq+R3U3jE+coOeEb3a3GgE2j/0MMiDKM/QpLb6h9ihf5fGY9UXtvK9o4vNqjWXoZz7/3EaSVU3IX53TvFFUOw==
+
chai@^4.3.10:
version "4.4.1"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1"
@@ -1819,6 +2950,25 @@ check-error@^1.0.3:
dependencies:
get-func-name "^2.0.2"
+chevrotain-allstar@~0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz#b7412755f5d83cc139ab65810cdb00d8db40e6ca"
+ integrity sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==
+ dependencies:
+ lodash-es "^4.17.21"
+
+chevrotain@~11.0.3:
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-11.0.3.tgz#88ffc1fb4b5739c715807eaeedbbf200e202fc1b"
+ integrity sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==
+ dependencies:
+ "@chevrotain/cst-dts-gen" "11.0.3"
+ "@chevrotain/gast" "11.0.3"
+ "@chevrotain/regexp-to-ast" "11.0.3"
+ "@chevrotain/types" "11.0.3"
+ "@chevrotain/utils" "11.0.3"
+ lodash-es "4.17.21"
+
"chokidar@>=3.0.0 <4.0.0":
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
@@ -1834,6 +2984,11 @@ check-error@^1.0.3:
optionalDependencies:
fsevents "~2.3.2"
+clsx@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
+ integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
+
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -1885,6 +3040,16 @@ confbox@^0.1.7:
resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.7.tgz#ccfc0a2bcae36a84838e83a3b7f770fb17d6c579"
integrity sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==
+confbox@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06"
+ integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==
+
+confbox@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.2.tgz#8652f53961c74d9e081784beed78555974a9c110"
+ integrity sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==
+
convert-source-map@^1.7.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
@@ -1911,6 +3076,16 @@ cose-base@^2.2.0:
dependencies:
layout-base "^2.0.0"
+crc-32@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-0.3.0.tgz#6a3d3687f5baec41f7e9b99fe1953a2e5d19775e"
+ integrity sha512-kucVIjOmMc1f0tv53BJ/5WIX+MGLcKuoBhnGqQrgKJNqLByb/sVMWfW/Aw6hw0jgcqjJ2pi9E5y32zOIpaUlsA==
+
+crelt@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
+ integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
+
cross-env@7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
@@ -1946,7 +3121,7 @@ cytoscape-cose-bilkent@^4.1.0:
dependencies:
cose-base "^1.0.0"
-cytoscape-fcose@^2.1.0:
+cytoscape-fcose@^2.1.0, cytoscape-fcose@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz#e4d6f6490df4fab58ae9cea9e5c3ab8d7472f471"
integrity sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==
@@ -1961,13 +3136,10 @@ cytoscape@^3.23.0:
heap "^0.2.6"
lodash "^4.17.21"
-cytoscape@^3.28.1:
- version "3.28.1"
- resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.28.1.tgz#f32c3e009bdf32d47845a16a4cd2be2bbc01baf7"
- integrity sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg==
- dependencies:
- heap "^0.2.6"
- lodash "^4.17.21"
+cytoscape@^3.29.3:
+ version "3.31.2"
+ resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.31.2.tgz#94d5b86d142599a2d6e750f6b2f3102518c7d48e"
+ integrity sha512-/eOXg2uGdMdpGlEes5Sf6zE+jUG+05f3htFNQIxLxduOH/SsaUZiPBfAwP1btVIVzsnhiNOdi+hvDRLYfMZjGw==
"d3-array@1 - 2":
version "2.12.1"
@@ -2240,6 +3412,42 @@ d3@^7.4.0, d3@^7.8.2:
d3-transition "3"
d3-zoom "3"
+d3@^7.9.0:
+ version "7.9.0"
+ resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d"
+ integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==
+ dependencies:
+ d3-array "3"
+ d3-axis "3"
+ d3-brush "3"
+ d3-chord "3"
+ d3-color "3"
+ d3-contour "4"
+ d3-delaunay "6"
+ d3-dispatch "3"
+ d3-drag "3"
+ d3-dsv "3"
+ d3-ease "3"
+ d3-fetch "3"
+ d3-force "3"
+ d3-format "3"
+ d3-geo "3"
+ d3-hierarchy "3"
+ d3-interpolate "3"
+ d3-path "3"
+ d3-polygon "3"
+ d3-quadtree "3"
+ d3-random "3"
+ d3-scale "4"
+ d3-scale-chromatic "3"
+ d3-selection "3"
+ d3-shape "3"
+ d3-time "3"
+ d3-time-format "4"
+ d3-timer "3"
+ d3-transition "3"
+ d3-zoom "3"
+
dagre-d3-es@7.0.10:
version "7.0.10"
resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz#19800d4be674379a3cd8c86a8216a2ac6827cadc"
@@ -2248,6 +3456,14 @@ dagre-d3-es@7.0.10:
d3 "^7.8.2"
lodash-es "^4.17.21"
+dagre-d3-es@7.0.13:
+ version "7.0.13"
+ resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz#acfb4b449f6dcdd48d8ea8081a6d8c59bc8128c3"
+ integrity sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==
+ dependencies:
+ d3 "^7.9.0"
+ lodash-es "^4.17.21"
+
data-urls@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde"
@@ -2256,6 +3472,11 @@ data-urls@^5.0.0:
whatwg-mimetype "^4.0.0"
whatwg-url "^14.0.0"
+dayjs@^1.11.18:
+ version "1.11.19"
+ resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.19.tgz#15dc98e854bb43917f12021806af897c58ae2938"
+ integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==
+
dayjs@^1.11.7:
version "1.11.9"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"
@@ -2268,6 +3489,13 @@ debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
dependencies:
ms "2.1.2"
+debug@^4.4.1:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
decimal.js@^10.4.3:
version "10.4.3"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
@@ -2309,6 +3537,11 @@ dequal@^2.0.0:
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
+detect-node-es@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
+ integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
+
diff-sequences@^29.6.3:
version "29.6.3"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
@@ -2338,10 +3571,12 @@ dompurify@3.0.3:
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.3.tgz#4b115d15a091ddc96f232bcef668550a2f6f1430"
integrity sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==
-"dompurify@^3.0.5 <3.1.7":
- version "3.1.6"
- resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.6.tgz#43c714a94c6a7b8801850f82e756685300a027e2"
- integrity sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==
+dompurify@^3.2.5:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.3.0.tgz#aaaadbb83d87e1c2fbb066452416359e5b62ec97"
+ integrity sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==
+ optionalDependencies:
+ "@types/trusted-types" "^2.0.7"
eastasianwidth@^0.2.0:
version "0.2.0"
@@ -2358,11 +3593,6 @@ elkjs@^0.8.2:
resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.8.2.tgz#c37763c5a3e24e042e318455e0147c912a7c248e"
integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==
-elkjs@^0.9.0:
- version "0.9.2"
- resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.9.2.tgz#3d4ef6f17fde06a5d7eaa3063bb875e25e59e972"
- integrity sha512-2Y/RaA1pdgSHpY0YG4TYuYCD2wh97CRvu22eLG3Kz0pgQ/6KbIFTxsTnDc4MH/6hFlg2L/9qXrDMG0nMjP63iw==
-
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -2378,6 +3608,11 @@ entities@^4.4.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+es6-promise-pool@2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/es6-promise-pool/-/es6-promise-pool-2.5.0.tgz#147c612b36b47f105027f9d2bf54a598a99d9ccb"
+ integrity sha512-VHErXfzR/6r/+yyzPKeBvO0lgjfC5cbDCQWjWwMZWSb6YU39TGIl51OUmCfWCq4ylMdJSB8zkz2vIuIeIxXApA==
+
esbuild@^0.20.1:
version "0.20.2"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1"
@@ -2560,6 +3795,11 @@ execa@^8.0.1:
signal-exit "^4.1.0"
strip-final-newline "^3.0.0"
+exsolve@^1.0.7:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.8.tgz#7f5e34da61cd1116deda5136e62292c096f50613"
+ integrity sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -2650,6 +3890,11 @@ form-data@^4.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
+fractional-indexing@3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/fractional-indexing/-/fractional-indexing-3.2.0.tgz#1193e63d54ff4e0cbe0c79a9ed6cfbab25d91628"
+ integrity sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2670,6 +3915,11 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+fuzzy@0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8"
+ integrity sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w==
+
gensync@^1.0.0-beta.1:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@@ -2680,6 +3930,11 @@ get-func-name@^2.0.1, get-func-name@^2.0.2:
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41"
integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==
+get-nonce@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
+ integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
+
get-stream@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2"
@@ -2734,6 +3989,11 @@ globals@^13.19.0:
dependencies:
type-fest "^0.20.2"
+globals@^15.15.0:
+ version "15.15.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8"
+ integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==
+
globby@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
@@ -2746,6 +4006,11 @@ globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
+glur@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/glur/-/glur-1.1.2.tgz#f20ea36db103bfc292343921f1f91e83c3467689"
+ integrity sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==
+
grapheme-splitter@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
@@ -2756,6 +4021,11 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+hachure-fill@^0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/hachure-fill/-/hachure-fill-0.5.2.tgz#d19bc4cc8750a5962b47fb1300557a85fcf934cc"
+ integrity sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==
+
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -2823,6 +4093,13 @@ ignore@^5.2.0:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
+image-blob-reduce@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/image-blob-reduce/-/image-blob-reduce-3.0.1.tgz#812be7655a552031635799ae64e846b106f7a489"
+ integrity sha512-/VmmWgIryG/wcn4TVrV7cC4mlfUC/oyiKIfSg5eVM3Ten/c1c34RJhMYKCWTnoSMHSqXLt3tsrBR4Q2HInvN+Q==
+ dependencies:
+ pica "^7.1.0"
+
immutable@^4.0.0:
version "4.3.5"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0"
@@ -2849,7 +4126,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2:
+inherits@2, inherits@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -2960,6 +4237,16 @@ jackspeak@^2.3.5:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
+jotai-scope@0.7.2:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/jotai-scope/-/jotai-scope-0.7.2.tgz#3e9ec5b743bd9f36b08b32cf5151786049bfcce7"
+ integrity sha512-Gwed97f3dDObrO43++2lRcgOqw4O2sdr4JCjP/7eHK1oPACDJ7xKHGScpJX9XaflU+KBHXF+VhwECnzcaQiShg==
+
+jotai@2.11.0:
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.11.0.tgz#923f8351e0b2d721036af892c0ae25625049d120"
+ integrity sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -3029,10 +4316,10 @@ json5@^2.1.2:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
-katex@^0.16.9:
- version "0.16.10"
- resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.10.tgz#6f81b71ac37ff4ec7556861160f53bc5f058b185"
- integrity sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==
+katex@^0.16.22:
+ version "0.16.25"
+ resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.25.tgz#61699984277e3bdb3e89e0e446b83cd0a57d87db"
+ integrity sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==
dependencies:
commander "^8.3.0"
@@ -3041,11 +4328,32 @@ khroma@^2.0.0:
resolved "https://registry.yarnpkg.com/khroma/-/khroma-2.0.0.tgz#7577de98aed9f36c7a474c4d453d94c0d6c6588b"
integrity sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==
+khroma@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/khroma/-/khroma-2.1.0.tgz#45f2ce94ce231a437cf5b63c2e886e6eb42bbbb1"
+ integrity sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==
+
kleur@^4.0.3:
version "4.1.5"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
+kolorist@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
+ integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
+
+langium@3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/langium/-/langium-3.3.1.tgz#da745a40d5ad8ee565090fed52eaee643be4e591"
+ integrity sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==
+ dependencies:
+ chevrotain "~11.0.3"
+ chevrotain-allstar "~0.3.0"
+ vscode-languageserver "~9.0.1"
+ vscode-languageserver-textdocument "~1.0.11"
+ vscode-uri "~3.0.8"
+
layout-base@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-1.0.2.tgz#1291e296883c322a9dd4c5dd82063721b53e26e2"
@@ -3072,6 +4380,15 @@ local-pkg@^0.5.0:
mlly "^1.4.2"
pkg-types "^1.0.3"
+local-pkg@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.1.2.tgz#c03d208787126445303f8161619dc701afa4abb5"
+ integrity sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==
+ dependencies:
+ mlly "^1.7.4"
+ pkg-types "^2.3.0"
+ quansync "^0.2.11"
+
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
@@ -3079,12 +4396,12 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
-lodash-es@^4.17.21:
+lodash-es@4.17.21, lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
-lodash.debounce@^4.0.8:
+lodash.debounce@4.0.8, lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
@@ -3094,12 +4411,17 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+lodash.throttle@4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
+ integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
+
lodash@^4.17.19, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -3155,6 +4477,11 @@ make-dir@^4.0.0:
dependencies:
semver "^7.5.3"
+marked@^16.2.1:
+ version "16.4.2"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-16.4.2.tgz#4959a64be6c486f0db7467ead7ce288de54290a3"
+ integrity sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==
+
mdast-util-from-markdown@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz#9421a5a247f10d31d2faed2a30df5ec89ceafcf0"
@@ -3213,31 +4540,31 @@ mermaid@*:
uuid "^9.0.0"
web-worker "^1.2.0"
-mermaid@10.9.4:
- version "10.9.4"
- resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.9.4.tgz#985fd4b6d73ae795b87f0b32f620a56d3d6bf1f8"
- integrity sha512-VIG2B0R9ydvkS+wShA8sXqkzfpYglM2Qwj7VyUeqzNVqSGPoP/tcaUr3ub4ESykv8eqQJn3p99bHNvYdg3gCHQ==
+mermaid@^11.12.1:
+ version "11.12.1"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-11.12.1.tgz#97445451ce7d0d3740bc2159cb25464bece60b67"
+ integrity sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==
dependencies:
- "@braintree/sanitize-url" "^6.0.1"
- "@types/d3-scale" "^4.0.3"
- "@types/d3-scale-chromatic" "^3.0.0"
- cytoscape "^3.28.1"
+ "@braintree/sanitize-url" "^7.1.1"
+ "@iconify/utils" "^3.0.1"
+ "@mermaid-js/parser" "^0.6.3"
+ "@types/d3" "^7.4.3"
+ cytoscape "^3.29.3"
cytoscape-cose-bilkent "^4.1.0"
- d3 "^7.4.0"
+ cytoscape-fcose "^2.2.0"
+ d3 "^7.9.0"
d3-sankey "^0.12.3"
- dagre-d3-es "7.0.10"
- dayjs "^1.11.7"
- dompurify "^3.0.5 <3.1.7"
- elkjs "^0.9.0"
- katex "^0.16.9"
- khroma "^2.0.0"
+ dagre-d3-es "7.0.13"
+ dayjs "^1.11.18"
+ dompurify "^3.2.5"
+ katex "^0.16.22"
+ khroma "^2.1.0"
lodash-es "^4.17.21"
- mdast-util-from-markdown "^1.3.0"
- non-layered-tidy-tree-layout "^2.0.2"
- stylis "^4.1.3"
+ marked "^16.2.1"
+ roughjs "^4.6.6"
+ stylis "^4.3.6"
ts-dedent "^2.2.0"
- uuid "^9.0.0"
- web-worker "^1.2.0"
+ uuid "^11.1.0"
micromark-core-commonmark@^1.0.1:
version "1.1.0"
@@ -3487,6 +4814,16 @@ mlly@^1.4.2, mlly@^1.6.1:
pkg-types "^1.1.0"
ufo "^1.5.3"
+mlly@^1.7.4:
+ version "1.7.4"
+ resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.4.tgz#3d7295ea2358ec7a271eaa5d000a0f84febe100f"
+ integrity sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==
+ dependencies:
+ acorn "^8.14.0"
+ pathe "^2.0.1"
+ pkg-types "^1.3.0"
+ ufo "^1.5.4"
+
mri@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
@@ -3497,6 +4834,24 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+multimath@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/multimath/-/multimath-2.0.0.tgz#0d37acf67c328f30e3d8c6b0d3209e6082710302"
+ integrity sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g==
+ dependencies:
+ glur "^1.1.2"
+ object-assign "^4.1.1"
+
+nanoid@3.3.3:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
+ integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
+
nanoid@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
@@ -3596,6 +4951,16 @@ p-locate@^5.0.0:
dependencies:
p-limit "^3.0.2"
+package-manager-detector@^1.3.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/package-manager-detector/-/package-manager-detector-1.5.0.tgz#8dcf7b78554047ddf5da453e6ba07ebc915c507e"
+ integrity sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==
+
+pako@2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.3.tgz#cdf475e31b678565251406de9e759196a0ea7a43"
+ integrity sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw==
+
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -3610,6 +4975,11 @@ parse5@^7.1.2:
dependencies:
entities "^4.4.0"
+path-data-parser@0.1.0, path-data-parser@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/path-data-parser/-/path-data-parser-0.1.0.tgz#8f5ba5cc70fc7becb3dcefaea08e2659aba60b8c"
+ integrity sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==
+
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -3653,11 +5023,32 @@ pathe@^1.1.1, pathe@^1.1.2:
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
+pathe@^2.0.1, pathe@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
+ integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
+
pathval@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
+perfect-freehand@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-1.2.0.tgz#706a0f854544f6175772440c51d3b0563eb3988a"
+ integrity sha512-h/0ikF1M3phW7CwpZ5MMvKnfpHficWoOEyr//KVNTxV4F6deRK1eYMtHyBKEAKFK0aXIEUK9oBvlF6PNXMDsAw==
+
+pica@7.1.1, pica@^7.1.0:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/pica/-/pica-7.1.1.tgz#c68b42f5cfa6cc26eaec5cfa10cc0a5299ef3b7a"
+ integrity sha512-WY73tMvNzXWEld2LicT9Y260L43isrZ85tPuqRyvtkljSDLmnNFQmZICt4xUJMVulmcc6L9O7jbBrtx3DOz/YQ==
+ dependencies:
+ glur "^1.1.2"
+ inherits "^2.0.3"
+ multimath "^2.0.0"
+ object-assign "^4.1.1"
+ webworkify "^1.5.0"
+
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
@@ -3677,6 +5068,62 @@ pkg-types@^1.0.3, pkg-types@^1.1.0:
mlly "^1.6.1"
pathe "^1.1.2"
+pkg-types@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df"
+ integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==
+ dependencies:
+ confbox "^0.1.8"
+ mlly "^1.7.4"
+ pathe "^2.0.1"
+
+pkg-types@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-2.3.0.tgz#037f2c19bd5402966ff6810e32706558cb5b5726"
+ integrity sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==
+ dependencies:
+ confbox "^0.2.2"
+ exsolve "^1.0.7"
+ pathe "^2.0.3"
+
+png-chunk-text@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/png-chunk-text/-/png-chunk-text-1.0.0.tgz#1c6006d8e34ba471d38e1c9c54b3f53e1085e18f"
+ integrity sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw==
+
+png-chunks-encode@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/png-chunks-encode/-/png-chunks-encode-1.0.0.tgz#d9ea5e35caeeed782658c1ab7bafa7a5edb1a878"
+ integrity sha512-J1jcHgbQRsIIgx5wxW9UmCymV3wwn4qCCJl6KYgEU/yHCh/L2Mwq/nMOkRPtmV79TLxRZj5w3tH69pvygFkDqA==
+ dependencies:
+ crc-32 "^0.3.0"
+ sliced "^1.0.1"
+
+png-chunks-extract@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/png-chunks-extract/-/png-chunks-extract-1.0.0.tgz#fad4a905e66652197351c65e35b92c64311e472d"
+ integrity sha512-ZiVwF5EJ0DNZyzAqld8BP1qyJBaGOFaq9zl579qfbkcmOwWLLO4I9L8i2O4j3HkI6/35i0nKG2n+dZplxiT89Q==
+ dependencies:
+ crc-32 "^0.3.0"
+
+points-on-curve@0.2.0, points-on-curve@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/points-on-curve/-/points-on-curve-0.2.0.tgz#7dbb98c43791859434284761330fa893cb81b4d1"
+ integrity sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==
+
+points-on-curve@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/points-on-curve/-/points-on-curve-1.0.1.tgz#03fcdc08e48e3bfdc7e8bd1d7ccd4d5f11e132c6"
+ integrity sha512-3nmX4/LIiyuwGLwuUrfhTlDeQFlAhi7lyK/zcRNGhalwapDWgAGR82bUpmn2mA03vII3fvNCG8jAONzKXwpxAg==
+
+points-on-path@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/points-on-path/-/points-on-path-0.2.1.tgz#553202b5424c53bed37135b318858eacff85dd52"
+ integrity sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==
+ dependencies:
+ path-data-parser "0.1.0"
+ points-on-curve "0.2.0"
+
postcss@^8.4.38:
version "8.4.38"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
@@ -3717,15 +5164,6 @@ process@0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
-prop-types@^15.5.7:
- version "15.8.1"
- resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
- integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
- dependencies:
- loose-envify "^1.4.0"
- object-assign "^4.1.1"
- react-is "^16.13.1"
-
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
@@ -3741,6 +5179,16 @@ punycode@^2.1.1, punycode@^2.3.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+pwacompat@2.0.17:
+ version "2.0.17"
+ resolved "https://registry.yarnpkg.com/pwacompat/-/pwacompat-2.0.17.tgz#707959ff97f239bf1fe7b260b63aeea416a15eab"
+ integrity sha512-6Du7IZdIy7cHiv7AhtDy4X2QRM8IAD5DII69mt5qWibC2d15ZU8DmBG1WdZKekG11cChSu4zkSUGPF9sweOl6w==
+
+quansync@^0.2.11:
+ version "0.2.11"
+ resolved "https://registry.yarnpkg.com/quansync/-/quansync-0.2.11.tgz#f9c3adda2e1272e4f8cf3f1457b04cbdb4ee692a"
+ integrity sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==
+
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
@@ -3751,6 +5199,67 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+radix-ui@1.4.3:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/radix-ui/-/radix-ui-1.4.3.tgz#17712d9e26ee61fdf4cd3969f4e16a794419508b"
+ integrity sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-accessible-icon" "1.1.7"
+ "@radix-ui/react-accordion" "1.2.12"
+ "@radix-ui/react-alert-dialog" "1.1.15"
+ "@radix-ui/react-arrow" "1.1.7"
+ "@radix-ui/react-aspect-ratio" "1.1.7"
+ "@radix-ui/react-avatar" "1.1.10"
+ "@radix-ui/react-checkbox" "1.3.3"
+ "@radix-ui/react-collapsible" "1.1.12"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-context-menu" "2.2.16"
+ "@radix-ui/react-dialog" "1.1.15"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-dropdown-menu" "2.1.16"
+ "@radix-ui/react-focus-guards" "1.1.3"
+ "@radix-ui/react-focus-scope" "1.1.7"
+ "@radix-ui/react-form" "0.1.8"
+ "@radix-ui/react-hover-card" "1.1.15"
+ "@radix-ui/react-label" "2.1.7"
+ "@radix-ui/react-menu" "2.1.16"
+ "@radix-ui/react-menubar" "1.1.16"
+ "@radix-ui/react-navigation-menu" "1.2.14"
+ "@radix-ui/react-one-time-password-field" "0.1.8"
+ "@radix-ui/react-password-toggle-field" "0.1.3"
+ "@radix-ui/react-popover" "1.1.15"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-progress" "1.1.7"
+ "@radix-ui/react-radio-group" "1.3.8"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-scroll-area" "1.2.10"
+ "@radix-ui/react-select" "2.2.6"
+ "@radix-ui/react-separator" "1.1.7"
+ "@radix-ui/react-slider" "1.3.6"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-switch" "1.2.6"
+ "@radix-ui/react-tabs" "1.1.13"
+ "@radix-ui/react-toast" "1.2.15"
+ "@radix-ui/react-toggle" "1.1.10"
+ "@radix-ui/react-toggle-group" "1.1.11"
+ "@radix-ui/react-toolbar" "1.1.11"
+ "@radix-ui/react-tooltip" "1.2.8"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-effect-event" "0.0.2"
+ "@radix-ui/react-use-escape-keydown" "1.1.1"
+ "@radix-ui/react-use-is-hydrated" "0.1.0"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+ "@radix-ui/react-use-size" "1.1.1"
+ "@radix-ui/react-visually-hidden" "1.2.3"
+
react-dom@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -3759,23 +5268,37 @@ react-dom@18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
-react-is@^16.13.1:
- version "16.13.1"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
- integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-
react-is@^18.0.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
-react-split@^2.0.14:
- version "2.0.14"
- resolved "https://registry.yarnpkg.com/react-split/-/react-split-2.0.14.tgz#ef198259bf43264d605f792fb3384f15f5b34432"
- integrity sha512-bKWydgMgaKTg/2JGQnaJPg51T6dmumTWZppFgEbbY0Fbme0F5TuatAScCLaqommbGQQf/ZT1zaejuPDriscISA==
+react-remove-scroll-bar@^2.3.7:
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223"
+ integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==
+ dependencies:
+ react-style-singleton "^2.2.2"
+ tslib "^2.0.0"
+
+react-remove-scroll@^2.6.3:
+ version "2.7.2"
+ resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz#6442da56791117661978ae99cd29be9026fecca0"
+ integrity sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==
dependencies:
- prop-types "^15.5.7"
- split.js "^1.6.0"
+ react-remove-scroll-bar "^2.3.7"
+ react-style-singleton "^2.2.3"
+ tslib "^2.1.0"
+ use-callback-ref "^1.3.3"
+ use-sidecar "^1.1.3"
+
+react-style-singleton@^2.2.2, react-style-singleton@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz#4265608be69a4d70cfe3047f2c6c88b2c3ace388"
+ integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==
+ dependencies:
+ get-nonce "^1.0.0"
+ tslib "^2.0.0"
react@18.2.0:
version "18.2.0"
@@ -3901,6 +5424,26 @@ rollup@^4.13.0:
"@rollup/rollup-win32-x64-msvc" "4.14.1"
fsevents "~2.3.2"
+roughjs@4.6.4:
+ version "4.6.4"
+ resolved "https://registry.yarnpkg.com/roughjs/-/roughjs-4.6.4.tgz#b6f39b44645854a6e0a4a28b078368701eb7f939"
+ integrity sha512-s6EZ0BntezkFYMf/9mGn7M8XGIoaav9QQBCnJROWB3brUWQ683Q2LbRD/hq0Z3bAJ/9NVpU/5LpiTWvQMyLDhw==
+ dependencies:
+ hachure-fill "^0.5.2"
+ path-data-parser "^0.1.0"
+ points-on-curve "^0.2.0"
+ points-on-path "^0.2.1"
+
+roughjs@^4.6.6:
+ version "4.6.6"
+ resolved "https://registry.yarnpkg.com/roughjs/-/roughjs-4.6.6.tgz#1059f49a5e0c80dee541a005b20cc322b222158b"
+ integrity sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==
+ dependencies:
+ hachure-fill "^0.5.2"
+ path-data-parser "^0.1.0"
+ points-on-curve "^0.2.0"
+ points-on-path "^0.2.1"
+
rrweb-cssom@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1"
@@ -3930,6 +5473,15 @@ sade@^1.7.3:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+sass@1.51.0:
+ version "1.51.0"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.51.0.tgz#25ea36cf819581fe1fe8329e8c3a4eaaf70d2845"
+ integrity sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==
+ dependencies:
+ chokidar ">=3.0.0 <4.0.0"
+ immutable "^4.0.0"
+ source-map-js ">=0.6.2 <2.0.0"
+
sass@1.74.1:
version "1.74.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.74.1.tgz#686fc227d3707dd25cb2925e1db8e4562be29319"
@@ -4002,6 +5554,11 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+sliced@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
+ integrity sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==
+
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
@@ -4012,11 +5569,6 @@ source-map@^0.5.0:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
-split.js@^1.6.0:
- version "1.6.5"
- resolved "https://registry.yarnpkg.com/split.js/-/split.js-1.6.5.tgz#f7f61da1044c9984cb42947df4de4fadb5a3f300"
- integrity sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==
-
stackback@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b"
@@ -4092,11 +5644,21 @@ strip-literal@^2.0.0:
dependencies:
js-tokens "^9.0.0"
+style-mod@^4.0.0, style-mod@^4.1.0:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.1.3.tgz#6e9012255bb799bdac37e288f7671b5d71bf9f73"
+ integrity sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==
+
stylis@^4.1.3:
version "4.3.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c"
integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==
+stylis@^4.3.6:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.6.tgz#7c7b97191cb4f195f03ecab7d52f7902ed378320"
+ integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==
+
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -4140,6 +5702,16 @@ tinybench@^2.5.1:
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.8.0.tgz#30e19ae3a27508ee18273ffed9ac7018949acd7b"
integrity sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==
+tinycolor2@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
+ integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==
+
+tinyexec@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.2.tgz#bdd2737fe2ba40bd6f918ae26642f264b99ca251"
+ integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==
+
tinypool@^0.8.3:
version "0.8.4"
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8"
@@ -4189,6 +5761,11 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+tslib@^2.0.0, tslib@^2.1.0:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
+ integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@@ -4196,6 +5773,13 @@ tsutils@^3.21.0:
dependencies:
tslib "^1.8.1"
+tunnel-rat@0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/tunnel-rat/-/tunnel-rat-0.1.2.tgz#1717efbc474ea2d8aa05a91622457a6e201c0aeb"
+ integrity sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==
+ dependencies:
+ zustand "^4.3.2"
+
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@@ -4223,6 +5807,11 @@ ufo@^1.5.3:
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344"
integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==
+ufo@^1.5.4:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b"
+ integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==
+
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
@@ -4281,6 +5870,31 @@ url-parse@^1.5.3:
querystringify "^2.1.1"
requires-port "^1.0.0"
+use-callback-ref@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf"
+ integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==
+ dependencies:
+ tslib "^2.0.0"
+
+use-sidecar@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb"
+ integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==
+ dependencies:
+ detect-node-es "^1.1.0"
+ tslib "^2.0.0"
+
+use-sync-external-store@^1.2.2, use-sync-external-store@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d"
+ integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
+
+uuid@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
+ integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
+
uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
@@ -4355,6 +5969,46 @@ vitest@^1.6.0:
vite-node "1.6.0"
why-is-node-running "^2.2.2"
+vscode-jsonrpc@8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"
+ integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==
+
+vscode-languageserver-protocol@3.17.5:
+ version "3.17.5"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea"
+ integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==
+ dependencies:
+ vscode-jsonrpc "8.2.0"
+ vscode-languageserver-types "3.17.5"
+
+vscode-languageserver-textdocument@~1.0.11:
+ version "1.0.12"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631"
+ integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==
+
+vscode-languageserver-types@3.17.5:
+ version "3.17.5"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a"
+ integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==
+
+vscode-languageserver@~9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz#500aef82097eb94df90d008678b0b6b5f474015b"
+ integrity sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==
+ dependencies:
+ vscode-languageserver-protocol "3.17.5"
+
+vscode-uri@~3.0.8:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
+ integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
+
+w3c-keyname@^2.2.4:
+ version "2.2.8"
+ resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
+ integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==
+
w3c-xmlserializer@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c"
@@ -4372,6 +6026,11 @@ webidl-conversions@^7.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
+webworkify@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/webworkify/-/webworkify-1.5.0.tgz#734ad87a774de6ebdd546e1d3e027da5b8f4a42c"
+ integrity sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g==
+
whatwg-encoding@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5"
@@ -4464,3 +6123,10 @@ yocto-queue@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
+
+zustand@^4.3.2:
+ version "4.5.7"
+ resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.7.tgz#7d6bb2026a142415dd8be8891d7870e6dbe65f55"
+ integrity sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==
+ dependencies:
+ use-sync-external-store "^1.2.2"