@cocoar/vue-mermaid 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @cocoar/vue-mermaid
2
+
3
+ A standalone [Mermaid](https://mermaid.js.org/) diagram component for Vue 3.
4
+ `<CoarMermaidDiagram :code>` renders a diagram from a Mermaid source string —
5
+ Cocoar-themed, lazy-loaded, with opt-in zoom/pan.
6
+
7
+ It knows **nothing about markdown** or any embedding layer. To render
8
+ ` ```mermaid ` fenced code blocks inside [`@cocoar/vue-markdown`](../markdown),
9
+ use the thin adapter [`@cocoar/vue-markdown-mermaid`](../markdown-mermaid) — it
10
+ plugs this component in as a fence renderer.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pnpm add @cocoar/vue-mermaid
16
+ ```
17
+
18
+ `vue` is the only peer dependency. Mermaid is a regular dependency, dynamically
19
+ imported on first render so it lands in its own lazy chunk. Import the stylesheet
20
+ once:
21
+
22
+ ```ts
23
+ import '@cocoar/vue-mermaid/styles';
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```vue
29
+ <template>
30
+ <CoarMermaidDiagram :code="code" zoomable />
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ import { CoarMermaidDiagram } from '@cocoar/vue-mermaid';
35
+
36
+ const code = `
37
+ flowchart LR
38
+ A[Start] --> B{Choice}
39
+ B -->|yes| C[Do it]
40
+ B -->|no| D[Skip]
41
+ `;
42
+ </script>
43
+ ```
44
+
45
+ | Prop | Type | Description |
46
+ | --- | --- | --- |
47
+ | `code` | `string` | The Mermaid diagram source. |
48
+ | `language` | `string` | Info-string label (default `'mermaid'`); handy when reused. |
49
+ | `zoomable` | `boolean` | Enable zoom/pan (default `false`). |
50
+
51
+ ## How it works
52
+
53
+ - **Rendering** is client-only (Mermaid needs a DOM) and lazy (Mermaid is
54
+ dynamically imported on first mount).
55
+ - **Theming** maps Cocoar design tokens onto Mermaid's `themeVariables` (colors
56
+ normalized to sRGB, since Cocoar tokens are `oklch(...)` which Mermaid can't
57
+ read) so diagrams match the app's fonts and palette.
58
+ - **Fonts**: rendering waits for `document.fonts.ready` so text isn't clipped by
59
+ boxes measured before the web font loaded.
60
+ - **Security**: Mermaid runs with `securityLevel: 'strict'` — author diagram text
61
+ is treated as untrusted (HTML in labels is sanitized).
62
+ - **Invalid source** degrades to an error box that still shows the raw source; it
63
+ never throws up to the app.
64
+
65
+ ## Zoom & pan
66
+
67
+ With `zoomable`, the diagram sits in a fixed-height viewport with:
68
+
69
+ - **+ / − / ⤢ buttons** (top-right) — the primary, touch-friendly zoom;
70
+ - **Ctrl / ⌘ + wheel** — zoom toward the cursor;
71
+ - **drag** — pan (mouse / pen);
72
+ - **double-click** — reset.
73
+
74
+ Plain mouse-wheel scrolling is deliberately **not** captured, so a diagram never
75
+ traps the page scroll. On touch, one-finger scrolling still scrolls the page.
76
+ Set the viewport height with the `--coar-mermaid-height` CSS variable (default
77
+ `420px`).
78
+
79
+ ## Exports
80
+
81
+ | Export | Description |
82
+ | --- | --- |
83
+ | `CoarMermaidDiagram` | The renderer component (`{ code, language, zoomable }`). |
84
+ | `buildMermaidThemeVariables(getToken, resolveColor?)` | Pure Cocoar-token → Mermaid-theme mapping. |
85
+ | `makeCssColorResolver()` / `readCssTokens(el?)` | Browser-backed color/token resolvers for the bridge. |
@@ -0,0 +1,15 @@
1
+ type __VLS_Props = {
2
+ code: string;
3
+ language?: string;
4
+ zoomable?: boolean;
5
+ };
6
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
7
+ language: string;
8
+ zoomable: boolean;
9
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
10
+ container: HTMLDivElement;
11
+ viewport: HTMLDivElement;
12
+ content: HTMLDivElement;
13
+ }, HTMLDivElement>;
14
+ export default _default;
15
+ //# sourceMappingURL=CoarMermaidDiagram.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CoarMermaidDiagram.vue.d.ts","sourceRoot":"","sources":["../src/CoarMermaidDiagram.vue"],"names":[],"mappings":"AAwSA,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;;cAA7B,MAAM;cAAa,OAAO;;;;;;AAyMxE,wBASG"}
package/dist/index.css ADDED
@@ -0,0 +1,2 @@
1
+ .coar-mermaid{margin:var(--coar-spacing-m,1rem) 0;position:relative}.coar-mermaid__viewport{justify-content:center;display:flex}.coar-mermaid__svg{max-width:100%}.coar-mermaid__svg svg{max-width:100%;height:auto}.coar-mermaid--zoomable .coar-mermaid__viewport{height:var(--coar-mermaid-height,420px);border:1px solid var(--coar-border-neutral-tertiary,#e2e2e2);border-radius:var(--coar-input-radius,8px);background:var(--coar-background-neutral-primary,#fff);cursor:grab;display:block;position:relative;overflow:hidden}.coar-mermaid--zoomable .coar-mermaid__viewport.is-panning{cursor:grabbing}.coar-mermaid--zoomable .coar-mermaid__svg{width:100%;max-width:none}.coar-mermaid--zoomable .coar-mermaid__viewport.is-panning .coar-mermaid__svg{will-change:transform}.coar-mermaid--zoomable .coar-mermaid__svg svg{max-width:none}.coar-mermaid__controls{top:var(--coar-spacing-xs,.25rem);right:var(--coar-spacing-xs,.25rem);border-radius:var(--coar-input-radius,8px);background:color-mix(in srgb, var(--coar-background-neutral-primary,#fff) 88%, transparent);border:1px solid var(--coar-border-neutral-tertiary,#e2e2e2);gap:2px;padding:2px;display:flex;position:absolute}.coar-mermaid__controls button{border-radius:calc(var(--coar-input-radius,8px) - 2px);width:26px;height:26px;color:var(--coar-text-neutral-secondary,#333);font-family:var(--coar-font-family-body,sans-serif);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:0;font-size:16px;line-height:1;display:inline-flex}.coar-mermaid__controls button:hover{background:var(--coar-background-neutral-secondary,#f5f5f5)}.coar-mermaid__loading{color:var(--coar-text-neutral-tertiary,#666);font-family:var(--coar-font-family-body,sans-serif);font-size:var(--coar-font-size-xs,14px);padding:var(--coar-spacing-m,1rem)}.coar-mermaid__error{border:1px solid var(--coar-border-semantic-error,#dc2626);border-radius:var(--coar-input-radius,8px);background:var(--coar-background-semantic-error-subtle,#fee2e2);width:100%;padding:var(--coar-spacing-m,1rem);font-family:var(--coar-font-family-body,sans-serif)}.coar-mermaid__error-title{margin:0 0 var(--coar-spacing-xs,.25rem);color:var(--coar-text-semantic-error-bold,#991b1b);font-weight:600}.coar-mermaid__error-message{margin:0 0 var(--coar-spacing-s,.5rem);color:var(--coar-text-semantic-error-subtle,#b91c1c);font-size:var(--coar-font-size-xs,14px)}.coar-mermaid__error-source{padding:var(--coar-spacing-s,.5rem);border-radius:var(--coar-input-radius,8px);background:var(--coar-background-neutral-secondary,#f8f9fa);color:var(--coar-text-neutral-secondary,#333);font-size:var(--coar-font-size-xs,14px);white-space:pre;margin:0;overflow-x:auto}
2
+ /*$vite$:1*/
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@cocoar/vue-mermaid` — a standalone Mermaid diagram component for Vue 3.
3
+ *
4
+ * `<CoarMermaidDiagram :code>` takes a Mermaid diagram source string and renders
5
+ * it: lazy-loaded engine, client-only, `securityLevel: 'strict'`, Cocoar-themed,
6
+ * with opt-in zoom/pan. It has **no dependency on — and no knowledge of —
7
+ * markdown** or any embedding layer. To render ` ```mermaid ` fenced code blocks
8
+ * inside `@cocoar/vue-markdown`, use the thin adapter `@cocoar/vue-markdown-mermaid`.
9
+ */
10
+ export { default as CoarMermaidDiagram } from './CoarMermaidDiagram.vue';
11
+ export { buildMermaidThemeVariables, makeCssColorResolver, readCssTokens, } from './internal/theme-bridge';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAIzE,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,aAAa,GACd,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,272 @@
1
+ import { createCommentVNode as e, createElementBlock as t, createElementVNode as n, defineComponent as r, nextTick as i, normalizeClass as a, onBeforeUnmount as o, onMounted as s, openBlock as c, ref as l, shallowRef as u, toDisplayString as d, watch as f } from "vue";
2
+ //#region src/internal/mermaid-loader.ts
3
+ var p = null;
4
+ function m(e) {
5
+ return p || (p = import("mermaid").then((t) => {
6
+ let n = t.default ?? t;
7
+ return n.initialize({
8
+ startOnLoad: !1,
9
+ securityLevel: "strict",
10
+ ...e()
11
+ }), n;
12
+ }).catch((e) => {
13
+ throw p = null, e;
14
+ }), p);
15
+ }
16
+ var h = Promise.resolve(), g = 0;
17
+ function _(e, t) {
18
+ let n = async () => {
19
+ let n = await m(e);
20
+ if (typeof document < "u" && document.fonts?.ready) try {
21
+ await document.fonts.ready;
22
+ } catch {}
23
+ let r = `coar-mermaid-${g++}`;
24
+ try {
25
+ let { svg: e } = await n.render(r, t);
26
+ return e;
27
+ } finally {
28
+ typeof document < "u" && (document.getElementById(r)?.remove(), document.getElementById(`d${r}`)?.remove());
29
+ }
30
+ }, r = h.then(n, n);
31
+ return h = r.then(() => void 0, () => void 0), r;
32
+ }
33
+ //#endregion
34
+ //#region src/internal/theme-bridge.ts
35
+ var v = [
36
+ {
37
+ mermaidVar: "fontFamily",
38
+ token: "--coar-font-family-body",
39
+ kind: "font"
40
+ },
41
+ {
42
+ mermaidVar: "primaryColor",
43
+ token: "--coar-background-accent-secondary",
44
+ kind: "color"
45
+ },
46
+ {
47
+ mermaidVar: "mainBkg",
48
+ token: "--coar-background-accent-secondary",
49
+ kind: "color"
50
+ },
51
+ {
52
+ mermaidVar: "primaryTextColor",
53
+ token: "--coar-text-neutral-primary",
54
+ kind: "color"
55
+ },
56
+ {
57
+ mermaidVar: "primaryBorderColor",
58
+ token: "--coar-border-accent-primary",
59
+ kind: "color"
60
+ },
61
+ {
62
+ mermaidVar: "nodeBorder",
63
+ token: "--coar-border-accent-primary",
64
+ kind: "color"
65
+ },
66
+ {
67
+ mermaidVar: "secondaryColor",
68
+ token: "--coar-background-neutral-secondary",
69
+ kind: "color"
70
+ },
71
+ {
72
+ mermaidVar: "tertiaryColor",
73
+ token: "--coar-background-neutral-tertiary",
74
+ kind: "color"
75
+ },
76
+ {
77
+ mermaidVar: "clusterBkg",
78
+ token: "--coar-background-neutral-secondary",
79
+ kind: "color"
80
+ },
81
+ {
82
+ mermaidVar: "clusterBorder",
83
+ token: "--coar-border-neutral-tertiary",
84
+ kind: "color"
85
+ },
86
+ {
87
+ mermaidVar: "lineColor",
88
+ token: "--coar-border-neutral-secondary",
89
+ kind: "color"
90
+ },
91
+ {
92
+ mermaidVar: "textColor",
93
+ token: "--coar-text-neutral-primary",
94
+ kind: "color"
95
+ },
96
+ {
97
+ mermaidVar: "background",
98
+ token: "--coar-background-neutral-primary",
99
+ kind: "color"
100
+ }
101
+ ];
102
+ function y(e, t = (e) => e) {
103
+ let n = {};
104
+ for (let { mermaidVar: r, token: i, kind: a } of v) {
105
+ let o = e(i).trim();
106
+ if (!o) continue;
107
+ let s = a === "color" ? t(o) : o;
108
+ s && (n[r] = s);
109
+ }
110
+ return n;
111
+ }
112
+ function b(e) {
113
+ let t = e ?? (typeof document < "u" ? document.documentElement : null);
114
+ if (!t || typeof getComputedStyle > "u") return () => "";
115
+ let n = getComputedStyle(t);
116
+ return (e) => n.getPropertyValue(e);
117
+ }
118
+ function x() {
119
+ if (typeof document > "u") return () => "";
120
+ let e = document.createElement("canvas");
121
+ e.width = 1, e.height = 1;
122
+ let t = e.getContext("2d", { willReadFrequently: !0 });
123
+ return t ? (e) => {
124
+ if (!e) return "";
125
+ t.fillStyle = "#000000", t.fillStyle = e;
126
+ let n = t.fillStyle;
127
+ if (t.fillStyle = "#ffffff", t.fillStyle = e, t.fillStyle !== n) return "";
128
+ t.clearRect(0, 0, 1, 1), t.fillRect(0, 0, 1, 1);
129
+ let [r, i, a, o] = t.getImageData(0, 0, 1, 1).data;
130
+ return o === 255 ? `rgb(${r}, ${i}, ${a})` : `rgba(${r}, ${i}, ${a}, ${(o / 255).toFixed(3)})`;
131
+ } : () => "";
132
+ }
133
+ //#endregion
134
+ //#region src/internal/pan-zoom.ts
135
+ function S(e, t, n = {}) {
136
+ let r = n.minScale ?? .3, i = n.maxScale ?? 8, a = n.zoomSpeed ?? .0015, o = n.step ?? 1.25, s = 1, c = 0, l = 0, u = !1, d = -1, f = 0, p = 0, m = 0, h = 0;
137
+ t.style.transformOrigin = "0 0";
138
+ let g = () => {
139
+ t.style.transform = `translate(${c}px, ${l}px) scale(${s})`;
140
+ }, _ = (e, t, n) => {
141
+ let a = C(s * n, r, i), o = a / s;
142
+ c = e - o * (e - c), l = t - o * (t - l), s = a, g();
143
+ }, v = (t) => {
144
+ let n = e.getBoundingClientRect();
145
+ _(n.width / 2, n.height / 2, t);
146
+ }, y = (t) => {
147
+ if (!(t.ctrlKey || t.metaKey)) return;
148
+ t.preventDefault();
149
+ let n = e.getBoundingClientRect();
150
+ _(t.clientX - n.left, t.clientY - n.top, Math.exp(-t.deltaY * a));
151
+ }, b = (t) => {
152
+ t.button !== 0 || t.pointerType === "touch" || (u = !0, d = t.pointerId, f = t.clientX, p = t.clientY, m = c, h = l, e.setPointerCapture(d), e.classList.add("is-panning"));
153
+ }, x = (e) => {
154
+ u && (c = m + (e.clientX - f), l = h + (e.clientY - p), g());
155
+ }, S = () => {
156
+ if (u) {
157
+ u = !1;
158
+ try {
159
+ e.releasePointerCapture(d);
160
+ } catch {}
161
+ e.classList.remove("is-panning");
162
+ }
163
+ }, w = () => {
164
+ s = 1;
165
+ let n = t.style.transform;
166
+ t.style.transform = "none";
167
+ let r = e.getBoundingClientRect(), i = t.getBoundingClientRect();
168
+ t.style.transform = n, c = Math.max(0, (r.width - i.width) / 2), l = Math.max(0, (r.height - i.height) / 2), g();
169
+ };
170
+ return e.addEventListener("wheel", y, { passive: !1 }), e.addEventListener("pointerdown", b), e.addEventListener("pointermove", x), e.addEventListener("pointerup", S), e.addEventListener("pointercancel", S), e.addEventListener("dblclick", w), w(), {
171
+ zoomIn: () => v(o),
172
+ zoomOut: () => v(1 / o),
173
+ reset: w,
174
+ destroy() {
175
+ e.removeEventListener("wheel", y), e.removeEventListener("pointerdown", b), e.removeEventListener("pointermove", x), e.removeEventListener("pointerup", S), e.removeEventListener("pointercancel", S), e.removeEventListener("dblclick", w);
176
+ }
177
+ };
178
+ }
179
+ function C(e, t, n) {
180
+ return Math.min(n, Math.max(t, e));
181
+ }
182
+ //#endregion
183
+ //#region src/CoarMermaidDiagram.vue?vue&type=script&setup=true&lang.ts
184
+ var w = ["data-status"], T = ["innerHTML"], E = {
185
+ key: 1,
186
+ class: "coar-mermaid__loading"
187
+ }, D = {
188
+ key: 2,
189
+ class: "coar-mermaid__error",
190
+ role: "alert"
191
+ }, O = { class: "coar-mermaid__error-message" }, k = { class: "coar-mermaid__error-source" }, A = {
192
+ key: 0,
193
+ class: "coar-mermaid__controls"
194
+ }, j = /* @__PURE__ */ r({
195
+ __name: "CoarMermaidDiagram",
196
+ props: {
197
+ code: {},
198
+ language: { default: "mermaid" },
199
+ zoomable: {
200
+ type: Boolean,
201
+ default: !1
202
+ }
203
+ },
204
+ setup(r) {
205
+ let p = r, m = l(null), h = l(null), g = l(null), v = l("idle"), C = l(""), j = l(""), M = 0, N = u(null);
206
+ function P() {
207
+ N.value?.destroy(), N.value = null;
208
+ }
209
+ async function F() {
210
+ if (typeof window > "u") return;
211
+ let e = p.code?.trim() ?? "";
212
+ if (P(), !e) {
213
+ v.value = "idle", C.value = "", j.value = "";
214
+ return;
215
+ }
216
+ let t = ++M;
217
+ v.value = "loading";
218
+ try {
219
+ let n = await _(() => ({
220
+ theme: "base",
221
+ themeVariables: y(b(m.value), x())
222
+ }), e);
223
+ if (t !== M) return;
224
+ C.value = n, j.value = "", v.value = "rendered", p.zoomable && (await i(), t === M && h.value && g.value && (N.value = S(h.value, g.value)));
225
+ } catch (e) {
226
+ if (t !== M) return;
227
+ C.value = "", j.value = e instanceof Error ? e.message : String(e), v.value = "error";
228
+ }
229
+ }
230
+ return s(F), f(() => p.code, F), o(P), (i, o) => (c(), t("div", {
231
+ ref_key: "container",
232
+ ref: m,
233
+ class: a(["coar-mermaid", { "coar-mermaid--zoomable": r.zoomable }]),
234
+ "data-status": v.value
235
+ }, [n("div", {
236
+ ref_key: "viewport",
237
+ ref: h,
238
+ class: "coar-mermaid__viewport"
239
+ }, [v.value === "rendered" ? (c(), t("div", {
240
+ key: 0,
241
+ ref_key: "content",
242
+ ref: g,
243
+ class: "coar-mermaid__svg",
244
+ innerHTML: C.value
245
+ }, null, 8, T)) : v.value === "loading" ? (c(), t("div", E, " Rendering diagram… ")) : v.value === "error" ? (c(), t("div", D, [
246
+ o[3] ||= n("p", { class: "coar-mermaid__error-title" }, "Diagram error", -1),
247
+ n("p", O, d(j.value), 1),
248
+ n("pre", k, d(r.code), 1)
249
+ ])) : e("", !0)], 512), r.zoomable && v.value === "rendered" ? (c(), t("div", A, [
250
+ n("button", {
251
+ type: "button",
252
+ title: "Zoom out",
253
+ "aria-label": "Zoom out",
254
+ onClick: o[0] ||= (e) => N.value?.zoomOut()
255
+ }, " − "),
256
+ n("button", {
257
+ type: "button",
258
+ title: "Zoom in",
259
+ "aria-label": "Zoom in",
260
+ onClick: o[1] ||= (e) => N.value?.zoomIn()
261
+ }, " + "),
262
+ n("button", {
263
+ type: "button",
264
+ title: "Reset view (or double-click)",
265
+ "aria-label": "Reset view",
266
+ onClick: o[2] ||= (e) => N.value?.reset()
267
+ }, " ⤢ ")
268
+ ])) : e("", !0)], 10, w));
269
+ }
270
+ });
271
+ //#endregion
272
+ export { j as CoarMermaidDiagram, y as buildMermaidThemeVariables, x as makeCssColorResolver, b as readCssTokens };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Lazy, one-time loader for the Mermaid engine.
3
+ *
4
+ * Mermaid is heavy (it pulls in d3, dagre, …) and DOM-bound, so it is *never*
5
+ * imported eagerly: the first diagram that mounts triggers a dynamic
6
+ * `import('mermaid')`, which the consumer's bundler splits into its own chunk.
7
+ * Subsequent diagrams reuse the same resolved instance.
8
+ *
9
+ * Mermaid is initialized exactly once, with `startOnLoad: false` (we drive
10
+ * rendering imperatively) and `securityLevel: 'strict'` (author diagram text is
11
+ * untrusted — strict mode sanitizes HTML in labels and blocks click bindings).
12
+ * The theme config is captured at that first initialization; a reactive
13
+ * theme-swap at runtime is a deliberate follow-up, not part of this slice.
14
+ */
15
+ /** The slice of Mermaid's API this package uses. */
16
+ export interface MermaidApi {
17
+ render(id: string, text: string): Promise<{
18
+ svg: string;
19
+ bindFunctions?: (element: Element) => void;
20
+ }>;
21
+ }
22
+ /** Init config passed to Mermaid — kept loose to stay version-tolerant. */
23
+ export type MermaidInitConfig = Record<string, unknown>;
24
+ /**
25
+ * Resolve the initialized Mermaid instance, importing + initializing it on the
26
+ * first call. `configFactory` runs once, on that first call, and must read any
27
+ * runtime theme tokens it needs then (it is not re-consulted afterward).
28
+ */
29
+ export declare function loadMermaid(configFactory: () => MermaidInitConfig): Promise<MermaidApi>;
30
+ /**
31
+ * Render one diagram to an SVG string, serialized against every other render.
32
+ * Loads + initializes Mermaid on first use (via {@link loadMermaid}); rejects if
33
+ * the source is invalid so the caller can show a fallback.
34
+ */
35
+ export declare function renderMermaid(configFactory: () => MermaidInitConfig, code: string): Promise<string>;
36
+ /** Test-only hook: forget the cached instance so a fresh load can be asserted. */
37
+ export declare function resetMermaidForTests(): void;
38
+ //# sourceMappingURL=mermaid-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mermaid-loader.d.ts","sourceRoot":"","sources":["../../src/internal/mermaid-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,MAAM,CACJ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC,CAAC;CACzE;AAED,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAIxD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAuBvF;AAWD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,iBAAiB,EACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAsCjB;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Minimal, dependency-free pan + zoom for a rendered diagram.
3
+ *
4
+ * Deliberately does NOT hijack the plain mouse wheel — that would trap page
5
+ * scrolling ("I scroll and get stuck on the diagram"). Instead:
6
+ * - plain wheel → the page scrolls normally (we don't touch it),
7
+ * - Ctrl/⌘ + wheel → zoom toward the cursor,
8
+ * - the +/−/reset controls (driven by {@link PanZoomHandle}) → zoom, and
9
+ * - mouse / pen drag → pan.
10
+ * Touch is intentionally left to the browser (one-finger touch scrolls the page),
11
+ * so a diagram never blocks scrolling on a tablet — zoom there is via the buttons.
12
+ *
13
+ * The caller owns the DOM: it passes the clipping `viewport` (overflow hidden)
14
+ * and the `content` wrapper (the element holding Mermaid's SVG). A CSS transform
15
+ * on `content` is all that's needed — no d3 / svg-pan-zoom.
16
+ */
17
+ export interface PanZoomHandle {
18
+ /** Zoom in one step, toward the viewport center. */
19
+ zoomIn(): void;
20
+ /** Zoom out one step, from the viewport center. */
21
+ zoomOut(): void;
22
+ /** Recenter + reset zoom to 1. */
23
+ reset(): void;
24
+ /** Remove every listener. Idempotent. */
25
+ destroy(): void;
26
+ }
27
+ export interface PanZoomOptions {
28
+ minScale?: number;
29
+ maxScale?: number;
30
+ /** Ctrl/⌘+wheel sensitivity; larger = faster zoom. */
31
+ zoomSpeed?: number;
32
+ /** Multiplier applied by the +/− buttons per click. */
33
+ step?: number;
34
+ }
35
+ export declare function createPanZoom(viewport: HTMLElement, content: HTMLElement, options?: PanZoomOptions): PanZoomHandle;
36
+ //# sourceMappingURL=pan-zoom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pan-zoom.d.ts","sourceRoot":"","sources":["../../src/internal/pan-zoom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,MAAM,IAAI,IAAI,CAAC;IACf,mDAAmD;IACnD,OAAO,IAAI,IAAI,CAAC;IAChB,kCAAkC;IAClC,KAAK,IAAI,IAAI,CAAC;IACd,yCAAyC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAiHf"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Bridges Cocoar design tokens → Mermaid `themeVariables`, so a rendered
3
+ * diagram picks up the app's fonts and palette instead of Mermaid's stock look.
4
+ *
5
+ * The mapping is expressed as a pure function over a *token getter* + a *color
6
+ * resolver* so it can be unit-tested without a DOM (feed it fakes).
7
+ * {@link readCssTokens} produces the real, browser-backed getter (reads CSS
8
+ * custom properties via `getComputedStyle`); {@link makeCssColorResolver}
9
+ * produces the real color resolver.
10
+ *
11
+ * Why a color resolver at all: Cocoar's color tokens resolve to CSS Color-4
12
+ * values — e.g. `oklch(from #1183CD 0.92 0.035 h)` — which Mermaid's color parser
13
+ * (khroma) can't read, so a raw hand-off makes every diagram error out. The
14
+ * resolver rasterizes each color to concrete sRGB `rgb()/rgba()` (a format
15
+ * Mermaid always understands) and rejects anything unparseable, so a bad token is
16
+ * dropped rather than poisoning the whole theme.
17
+ *
18
+ * Only tokens that resolve to a non-empty value are emitted — a missing (or
19
+ * unparseable) token is simply omitted, letting Mermaid's `base` theme fill the
20
+ * gap rather than being handed an empty string.
21
+ */
22
+ /**
23
+ * Build the `themeVariables` map from a token getter + a color resolver.
24
+ * `getToken(name)` returns the resolved CSS value for a custom property (or `''`
25
+ * when unset); `resolveColor(value)` normalizes a CSS color to a Mermaid-safe
26
+ * form and returns `''` for anything unparseable. Both default to identity /
27
+ * pass-through so the function stays usable in a plain (DOM-free) unit test.
28
+ */
29
+ export declare function buildMermaidThemeVariables(getToken: (name: string) => string, resolveColor?: (value: string) => string): Record<string, string>;
30
+ /**
31
+ * Produce a token getter backed by `getComputedStyle` on the given element
32
+ * (defaults to `document.documentElement`). Returns a getter that always yields
33
+ * `''` when there is no DOM (SSR) or no element, so callers stay side-effect and
34
+ * crash free on the server.
35
+ */
36
+ export declare function readCssTokens(el?: Element | null): (name: string) => string;
37
+ /**
38
+ * Produce a color resolver that normalizes any CSS color the browser can paint
39
+ * — including CSS Color-4 forms like `oklch(...)` — into a concrete sRGB
40
+ * `rgb()/rgba()` string that Mermaid understands. Unparseable input yields `''`.
41
+ *
42
+ * Mechanism: paint the color onto a 1×1 canvas and read the pixel back. Canvas
43
+ * rasterizes to 8-bit sRGB, so the read-back is always plain rgb regardless of
44
+ * the source color space. Validity is probed first by assigning the value over
45
+ * two distinct sentinels — a color the canvas rejects leaves them differing.
46
+ *
47
+ * Returns a getter yielding `''` when there is no DOM / no 2D context (SSR).
48
+ */
49
+ export declare function makeCssColorResolver(): (value: string) => string;
50
+ //# sourceMappingURL=theme-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-bridge.d.ts","sourceRoot":"","sources":["../../src/internal/theme-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAyBH;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAClC,YAAY,GAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAyB,GACzD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASxB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAQ3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CA4BhE"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@cocoar/vue-mermaid",
3
+ "version": "2.14.0",
4
+ "description": "Standalone Mermaid diagram component for Vue 3: <CoarMermaidDiagram :code>. Cocoar-themed, lazy-loaded, with opt-in zoom/pan. Knows nothing about markdown — feed it a diagram source string.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/cocoar-dev/cocoar-ui-vue.git",
9
+ "directory": "packages/mermaid"
10
+ },
11
+ "homepage": "https://docs.cocoar.dev/cocoar-ui-vue/",
12
+ "bugs": "https://github.com/cocoar-dev/cocoar-ui-vue/issues",
13
+ "keywords": [
14
+ "vue",
15
+ "vue3",
16
+ "mermaid",
17
+ "diagram"
18
+ ],
19
+ "type": "module",
20
+ "sideEffects": [
21
+ "*.css",
22
+ "*.vue"
23
+ ],
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "import": "./dist/index.js",
30
+ "types": "./dist/index.d.ts"
31
+ },
32
+ "./styles": "./dist/index.css"
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "scripts": {
38
+ "build": "vite build",
39
+ "test": "vitest run",
40
+ "lint": "eslint src/",
41
+ "typecheck": "vue-tsc --noEmit"
42
+ },
43
+ "dependencies": {
44
+ "mermaid": "^11.4.0"
45
+ },
46
+ "peerDependencies": {
47
+ "vue": "^3.5.0"
48
+ },
49
+ "devDependencies": {
50
+ "vue": "^3.5.32"
51
+ }
52
+ }