@arach/arc-viewer 0.4.4

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/dist/index.js ADDED
@@ -0,0 +1,3017 @@
1
+ import React, { useState, useMemo, useCallback, useId, useRef, useEffect } from 'react';
2
+ import * as LucideIcons from 'lucide-react';
3
+ import { Minus, Plus, Scan, ChevronLeft, Pause, Play, ChevronRight, Expand } from 'lucide-react';
4
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/ArcDiagram.tsx
7
+
8
+ // src/themes.ts
9
+ var defaultTheme = {
10
+ id: "default",
11
+ name: "Default",
12
+ description: "Clean, neutral colors",
13
+ light: {
14
+ palette: {
15
+ violet: { border: "border-violet-300", bg: "bg-gradient-to-br from-violet-50 to-violet-100/50", icon: "text-violet-600", stroke: "#8b5cf6" },
16
+ emerald: { border: "border-emerald-300", bg: "bg-gradient-to-br from-emerald-50 to-emerald-100/50", icon: "text-emerald-600", stroke: "#10b981" },
17
+ blue: { border: "border-blue-300", bg: "bg-gradient-to-br from-blue-50 to-blue-100/50", icon: "text-blue-600", stroke: "#3b82f6" },
18
+ amber: { border: "border-amber-300", bg: "bg-gradient-to-br from-amber-50 to-amber-100/50", icon: "text-amber-600", stroke: "#f59e0b" },
19
+ sky: { border: "border-sky-300", bg: "bg-gradient-to-br from-sky-50 to-sky-100/50", icon: "text-sky-600", stroke: "#0ea5e9" },
20
+ zinc: { border: "border-zinc-300", bg: "bg-gradient-to-br from-zinc-50 to-zinc-100/50", icon: "text-zinc-600", stroke: "#71717a" },
21
+ rose: { border: "border-rose-300", bg: "bg-gradient-to-br from-rose-50 to-rose-100/50", icon: "text-rose-600", stroke: "#f43f5e" },
22
+ orange: { border: "border-orange-300", bg: "bg-gradient-to-br from-orange-50 to-orange-100/50", icon: "text-orange-600", stroke: "#f97316" }
23
+ },
24
+ background: {
25
+ container: "bg-white/80 border border-zinc-200 shadow-lg",
26
+ grid: { color: "rgba(16, 21, 24, 0.12)", opacity: 0.35, size: 24 }
27
+ },
28
+ text: { primary: "text-zinc-900", secondary: "text-zinc-600", muted: "text-zinc-500" }
29
+ },
30
+ dark: {
31
+ palette: {
32
+ violet: { border: "border-violet-400/50", bg: "bg-violet-500/10", icon: "text-violet-400", stroke: "#a78bfa" },
33
+ emerald: { border: "border-emerald-400/50", bg: "bg-emerald-500/10", icon: "text-emerald-400", stroke: "#34d399" },
34
+ blue: { border: "border-blue-400/50", bg: "bg-blue-500/10", icon: "text-blue-400", stroke: "#60a5fa" },
35
+ amber: { border: "border-amber-400/50", bg: "bg-amber-500/10", icon: "text-amber-400", stroke: "#fbbf24" },
36
+ sky: { border: "border-sky-400/50", bg: "bg-sky-500/10", icon: "text-sky-400", stroke: "#38bdf8" },
37
+ zinc: { border: "border-zinc-600", bg: "bg-zinc-800/50", icon: "text-zinc-400", stroke: "#71717a" },
38
+ rose: { border: "border-rose-400/50", bg: "bg-rose-500/10", icon: "text-rose-400", stroke: "#fb7185" },
39
+ orange: { border: "border-orange-400/50", bg: "bg-orange-500/10", icon: "text-orange-400", stroke: "#fb923c" }
40
+ },
41
+ background: {
42
+ container: "bg-zinc-950 border border-zinc-800",
43
+ grid: { color: "#71717a", opacity: 0.08, size: 24 }
44
+ },
45
+ text: { primary: "text-white", secondary: "text-zinc-400", muted: "text-zinc-600" }
46
+ }
47
+ };
48
+ var warmTheme = {
49
+ id: "warm",
50
+ name: "Warm",
51
+ description: "Soft, editorial warmth",
52
+ light: {
53
+ palette: {
54
+ violet: { border: "border-violet-300/70", bg: "bg-gradient-to-br from-violet-50/90 to-violet-100/50", icon: "text-violet-500", stroke: "#a78bfa" },
55
+ emerald: { border: "border-emerald-300/70", bg: "bg-gradient-to-br from-emerald-50/90 to-emerald-100/50", icon: "text-emerald-500", stroke: "#6ee7b7" },
56
+ blue: { border: "border-blue-300/70", bg: "bg-gradient-to-br from-blue-50/90 to-blue-100/50", icon: "text-blue-500", stroke: "#93c5fd" },
57
+ amber: { border: "border-amber-300/70", bg: "bg-gradient-to-br from-amber-50/90 to-amber-100/50", icon: "text-amber-500", stroke: "#fcd34d" },
58
+ sky: { border: "border-sky-300/70", bg: "bg-gradient-to-br from-sky-50/90 to-sky-100/50", icon: "text-sky-500", stroke: "#7dd3fc" },
59
+ zinc: { border: "border-stone-300/70", bg: "bg-gradient-to-br from-stone-50/90 to-stone-100/50", icon: "text-stone-500", stroke: "#a8a29e" },
60
+ rose: { border: "border-rose-300/70", bg: "bg-gradient-to-br from-rose-50/90 to-rose-100/50", icon: "text-rose-500", stroke: "#fda4af" },
61
+ orange: { border: "border-orange-300/70", bg: "bg-gradient-to-br from-orange-50/90 to-orange-100/50", icon: "text-orange-500", stroke: "#fdba74" }
62
+ },
63
+ background: {
64
+ container: "bg-gradient-to-br from-amber-50/80 via-orange-50/40 to-stone-100/60 border border-stone-200/60",
65
+ grid: { color: "rgba(180, 160, 140, 0.3)", opacity: 0.5, size: 24 }
66
+ },
67
+ text: { primary: "text-stone-800", secondary: "text-stone-600", muted: "text-stone-400" }
68
+ },
69
+ dark: {
70
+ palette: {
71
+ violet: { border: "border-violet-400/40", bg: "bg-violet-500/8", icon: "text-violet-300", stroke: "#c4b5fd" },
72
+ emerald: { border: "border-emerald-400/40", bg: "bg-emerald-500/8", icon: "text-emerald-300", stroke: "#a7f3d0" },
73
+ blue: { border: "border-blue-400/40", bg: "bg-blue-500/8", icon: "text-blue-300", stroke: "#bfdbfe" },
74
+ amber: { border: "border-amber-400/40", bg: "bg-amber-500/8", icon: "text-amber-300", stroke: "#fde68a" },
75
+ sky: { border: "border-sky-400/40", bg: "bg-sky-500/8", icon: "text-sky-300", stroke: "#bae6fd" },
76
+ zinc: { border: "border-stone-500/40", bg: "bg-stone-600/10", icon: "text-stone-300", stroke: "#d6d3d1" },
77
+ rose: { border: "border-rose-400/40", bg: "bg-rose-500/8", icon: "text-rose-300", stroke: "#fecdd3" },
78
+ orange: { border: "border-orange-400/40", bg: "bg-orange-500/8", icon: "text-orange-300", stroke: "#fed7aa" }
79
+ },
80
+ background: {
81
+ container: "bg-gradient-to-br from-stone-950 via-stone-900 to-stone-950 border border-stone-700/50",
82
+ grid: { color: "rgba(168, 162, 158, 0.15)", opacity: 0.15, size: 24 }
83
+ },
84
+ text: { primary: "text-stone-100", secondary: "text-stone-400", muted: "text-stone-600" }
85
+ }
86
+ };
87
+ var coolTheme = {
88
+ id: "cool",
89
+ name: "Cool",
90
+ description: "Crisp, modern blues",
91
+ light: {
92
+ palette: {
93
+ violet: { border: "border-indigo-300", bg: "bg-gradient-to-br from-indigo-50 to-indigo-100/50", icon: "text-indigo-600", stroke: "#6366f1" },
94
+ emerald: { border: "border-teal-300", bg: "bg-gradient-to-br from-teal-50 to-teal-100/50", icon: "text-teal-600", stroke: "#14b8a6" },
95
+ blue: { border: "border-blue-300", bg: "bg-gradient-to-br from-blue-50 to-blue-100/50", icon: "text-blue-600", stroke: "#3b82f6" },
96
+ amber: { border: "border-cyan-300", bg: "bg-gradient-to-br from-cyan-50 to-cyan-100/50", icon: "text-cyan-600", stroke: "#06b6d4" },
97
+ sky: { border: "border-sky-300", bg: "bg-gradient-to-br from-sky-50 to-sky-100/50", icon: "text-sky-600", stroke: "#0ea5e9" },
98
+ zinc: { border: "border-slate-300", bg: "bg-gradient-to-br from-slate-50 to-slate-100/50", icon: "text-slate-600", stroke: "#64748b" },
99
+ rose: { border: "border-fuchsia-300", bg: "bg-gradient-to-br from-fuchsia-50 to-fuchsia-100/50", icon: "text-fuchsia-600", stroke: "#d946ef" },
100
+ orange: { border: "border-violet-300", bg: "bg-gradient-to-br from-violet-50 to-violet-100/50", icon: "text-violet-600", stroke: "#8b5cf6" }
101
+ },
102
+ background: {
103
+ container: "bg-gradient-to-br from-slate-50 via-blue-50/30 to-slate-100 border border-slate-200",
104
+ grid: { color: "rgba(71, 85, 105, 0.15)", opacity: 0.4, size: 24 }
105
+ },
106
+ text: { primary: "text-slate-900", secondary: "text-slate-600", muted: "text-slate-400" }
107
+ },
108
+ dark: {
109
+ palette: {
110
+ violet: { border: "border-indigo-400/50", bg: "bg-indigo-500/10", icon: "text-indigo-400", stroke: "#818cf8" },
111
+ emerald: { border: "border-teal-400/50", bg: "bg-teal-500/10", icon: "text-teal-400", stroke: "#2dd4bf" },
112
+ blue: { border: "border-blue-400/50", bg: "bg-blue-500/10", icon: "text-blue-400", stroke: "#60a5fa" },
113
+ amber: { border: "border-cyan-400/50", bg: "bg-cyan-500/10", icon: "text-cyan-400", stroke: "#22d3ee" },
114
+ sky: { border: "border-sky-400/50", bg: "bg-sky-500/10", icon: "text-sky-400", stroke: "#38bdf8" },
115
+ zinc: { border: "border-slate-500", bg: "bg-slate-700/30", icon: "text-slate-400", stroke: "#94a3b8" },
116
+ rose: { border: "border-fuchsia-400/50", bg: "bg-fuchsia-500/10", icon: "text-fuchsia-400", stroke: "#e879f9" },
117
+ orange: { border: "border-violet-400/50", bg: "bg-violet-500/10", icon: "text-violet-400", stroke: "#a78bfa" }
118
+ },
119
+ background: {
120
+ container: "bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 border border-slate-700",
121
+ grid: { color: "rgba(148, 163, 184, 0.1)", opacity: 0.1, size: 24 }
122
+ },
123
+ text: { primary: "text-slate-100", secondary: "text-slate-400", muted: "text-slate-600" }
124
+ }
125
+ };
126
+ var monoTheme = {
127
+ id: "mono",
128
+ name: "Mono",
129
+ description: "Elegant grayscale",
130
+ light: {
131
+ palette: {
132
+ violet: { border: "border-zinc-400", bg: "bg-gradient-to-br from-zinc-100 to-zinc-200/50", icon: "text-zinc-700", stroke: "#52525b" },
133
+ emerald: { border: "border-zinc-400", bg: "bg-gradient-to-br from-zinc-100 to-zinc-200/50", icon: "text-zinc-700", stroke: "#52525b" },
134
+ blue: { border: "border-zinc-400", bg: "bg-gradient-to-br from-zinc-100 to-zinc-200/50", icon: "text-zinc-700", stroke: "#52525b" },
135
+ amber: { border: "border-zinc-400", bg: "bg-gradient-to-br from-zinc-100 to-zinc-200/50", icon: "text-zinc-700", stroke: "#52525b" },
136
+ sky: { border: "border-zinc-400", bg: "bg-gradient-to-br from-zinc-100 to-zinc-200/50", icon: "text-zinc-700", stroke: "#52525b" },
137
+ zinc: { border: "border-zinc-400", bg: "bg-gradient-to-br from-zinc-100 to-zinc-200/50", icon: "text-zinc-700", stroke: "#52525b" },
138
+ rose: { border: "border-zinc-400", bg: "bg-gradient-to-br from-zinc-100 to-zinc-200/50", icon: "text-zinc-700", stroke: "#52525b" },
139
+ orange: { border: "border-zinc-400", bg: "bg-gradient-to-br from-zinc-100 to-zinc-200/50", icon: "text-zinc-700", stroke: "#52525b" }
140
+ },
141
+ background: {
142
+ container: "bg-zinc-100 border border-zinc-300",
143
+ grid: { color: "rgba(63, 63, 70, 0.08)", opacity: 0.5, size: 20 }
144
+ },
145
+ text: { primary: "text-zinc-900", secondary: "text-zinc-600", muted: "text-zinc-400" }
146
+ },
147
+ dark: {
148
+ palette: {
149
+ violet: { border: "border-zinc-600", bg: "bg-zinc-800/60", icon: "text-zinc-300", stroke: "#a1a1aa" },
150
+ emerald: { border: "border-zinc-600", bg: "bg-zinc-800/60", icon: "text-zinc-300", stroke: "#a1a1aa" },
151
+ blue: { border: "border-zinc-600", bg: "bg-zinc-800/60", icon: "text-zinc-300", stroke: "#a1a1aa" },
152
+ amber: { border: "border-zinc-600", bg: "bg-zinc-800/60", icon: "text-zinc-300", stroke: "#a1a1aa" },
153
+ sky: { border: "border-zinc-600", bg: "bg-zinc-800/60", icon: "text-zinc-300", stroke: "#a1a1aa" },
154
+ zinc: { border: "border-zinc-600", bg: "bg-zinc-800/60", icon: "text-zinc-300", stroke: "#a1a1aa" },
155
+ rose: { border: "border-zinc-600", bg: "bg-zinc-800/60", icon: "text-zinc-300", stroke: "#a1a1aa" },
156
+ orange: { border: "border-zinc-600", bg: "bg-zinc-800/60", icon: "text-zinc-300", stroke: "#a1a1aa" }
157
+ },
158
+ background: {
159
+ container: "bg-zinc-900 border border-zinc-700",
160
+ grid: { color: "rgba(161, 161, 170, 0.06)", opacity: 0.1, size: 20 }
161
+ },
162
+ text: { primary: "text-zinc-100", secondary: "text-zinc-400", muted: "text-zinc-600" }
163
+ }
164
+ };
165
+ var THEMES = {
166
+ default: defaultTheme,
167
+ warm: warmTheme,
168
+ cool: coolTheme,
169
+ mono: monoTheme
170
+ };
171
+ var DEFAULT_THEME = "default";
172
+ function getTheme(id) {
173
+ return THEMES[id] || THEMES[DEFAULT_THEME];
174
+ }
175
+ function getThemeList() {
176
+ return Object.values(THEMES).map((t) => ({
177
+ id: t.id,
178
+ name: t.name,
179
+ description: t.description
180
+ }));
181
+ }
182
+
183
+ // src/autoLayout.ts
184
+ var NODE_SIZES = {
185
+ l: { width: 220, height: 90 },
186
+ m: { width: 160, height: 75 },
187
+ s: { width: 110, height: 48 },
188
+ xs: { width: 44, height: 44 }
189
+ };
190
+ var DEFAULTS = {
191
+ columnGap: 40,
192
+ nodeGap: 24,
193
+ rowGap: 40,
194
+ padding: 32
195
+ };
196
+ function assignLayers(nodeIds, connectors) {
197
+ const outgoing = /* @__PURE__ */ new Map();
198
+ const incoming = /* @__PURE__ */ new Map();
199
+ for (const id of nodeIds) {
200
+ outgoing.set(id, []);
201
+ incoming.set(id, []);
202
+ }
203
+ for (const c of connectors) {
204
+ if (outgoing.has(c.from) && incoming.has(c.to)) {
205
+ outgoing.get(c.from).push(c.to);
206
+ incoming.get(c.to).push(c.from);
207
+ }
208
+ }
209
+ const roots = nodeIds.filter((id) => incoming.get(id).length === 0);
210
+ if (roots.length === 0) roots.push(nodeIds[0]);
211
+ const layer = /* @__PURE__ */ new Map();
212
+ for (const id of nodeIds) layer.set(id, 0);
213
+ const visited = /* @__PURE__ */ new Set();
214
+ const inDegree = /* @__PURE__ */ new Map();
215
+ for (const id of nodeIds) inDegree.set(id, incoming.get(id).length);
216
+ const queue = roots.filter((r) => inDegree.get(r) === 0);
217
+ if (queue.length === 0) queue.push(nodeIds[0]);
218
+ while (queue.length > 0) {
219
+ const node = queue.shift();
220
+ if (visited.has(node)) continue;
221
+ visited.add(node);
222
+ for (const next of outgoing.get(node) || []) {
223
+ const newLayer = layer.get(node) + 1;
224
+ if (newLayer > layer.get(next)) layer.set(next, newLayer);
225
+ const deg = inDegree.get(next) - 1;
226
+ inDegree.set(next, deg);
227
+ if (deg <= 0) queue.push(next);
228
+ }
229
+ }
230
+ for (const id of nodeIds) {
231
+ if (!visited.has(id)) layer.set(id, 0);
232
+ }
233
+ return layer;
234
+ }
235
+ function orderWithinLayers(layers, connectors) {
236
+ const layerCount = Math.max(...layers.keys()) + 1;
237
+ const outgoing = /* @__PURE__ */ new Map();
238
+ const incoming = /* @__PURE__ */ new Map();
239
+ for (const [, nodes] of layers) {
240
+ for (const id of nodes) {
241
+ outgoing.set(id, []);
242
+ incoming.set(id, []);
243
+ }
244
+ }
245
+ for (const c of connectors) {
246
+ outgoing.get(c.from)?.push(c.to);
247
+ incoming.get(c.to)?.push(c.from);
248
+ }
249
+ const position = /* @__PURE__ */ new Map();
250
+ for (const [, nodes] of layers) {
251
+ nodes.forEach((id, idx) => position.set(id, idx));
252
+ }
253
+ for (let sweep = 0; sweep < 4; sweep++) {
254
+ for (let l = 1; l < layerCount; l++) {
255
+ const nodes = layers.get(l) || [];
256
+ const indexed = nodes.map((id) => {
257
+ const preds = incoming.get(id) || [];
258
+ const bc = preds.length === 0 ? position.get(id) || 0 : preds.reduce((s, p) => s + (position.get(p) || 0), 0) / preds.length;
259
+ return { id, bc };
260
+ });
261
+ indexed.sort((a, b) => a.bc - b.bc);
262
+ const sorted = indexed.map((x) => x.id);
263
+ layers.set(l, sorted);
264
+ sorted.forEach((id, idx) => position.set(id, idx));
265
+ }
266
+ for (let l = layerCount - 2; l >= 0; l--) {
267
+ const nodes = layers.get(l) || [];
268
+ const indexed = nodes.map((id) => {
269
+ const succs = outgoing.get(id) || [];
270
+ const bc = succs.length === 0 ? position.get(id) || 0 : succs.reduce((s, p) => s + (position.get(p) || 0), 0) / succs.length;
271
+ return { id, bc };
272
+ });
273
+ indexed.sort((a, b) => a.bc - b.bc);
274
+ const sorted = indexed.map((x) => x.id);
275
+ layers.set(l, sorted);
276
+ sorted.forEach((id, idx) => position.set(id, idx));
277
+ }
278
+ }
279
+ return layers;
280
+ }
281
+ function inferAnchors(fromNode, toNode) {
282
+ const fromSize = NODE_SIZES[fromNode.size] || NODE_SIZES.m;
283
+ const toSize = NODE_SIZES[toNode.size] || NODE_SIZES.m;
284
+ const fromCx = fromNode.x + fromSize.width / 2;
285
+ const fromCy = fromNode.y + fromSize.height / 2;
286
+ const toCx = toNode.x + toSize.width / 2;
287
+ const toCy = toNode.y + toSize.height / 2;
288
+ const dx = toCx - fromCx;
289
+ const dy = toCy - fromCy;
290
+ if (Math.abs(dx) > Math.abs(dy) * 0.5) {
291
+ if (dx > 0) {
292
+ if (Math.abs(dy) < fromSize.height * 0.3) return { fromAnchor: "right", toAnchor: "left" };
293
+ return dy > 0 ? { fromAnchor: "bottomRight", toAnchor: "left" } : { fromAnchor: "topRight", toAnchor: "left" };
294
+ } else {
295
+ if (Math.abs(dy) < fromSize.height * 0.3) return { fromAnchor: "left", toAnchor: "right" };
296
+ return dy > 0 ? { fromAnchor: "bottomLeft", toAnchor: "right" } : { fromAnchor: "topLeft", toAnchor: "right" };
297
+ }
298
+ }
299
+ return dy > 0 ? { fromAnchor: "bottom", toAnchor: "top" } : { fromAnchor: "top", toAnchor: "bottom" };
300
+ }
301
+ function fitLayersPerRow(columnWidths, targetW, padding, columnGap) {
302
+ const available = targetW - 2 * padding;
303
+ let width = 0;
304
+ for (let i = 0; i < columnWidths.length; i++) {
305
+ const added = columnWidths[i] + (i > 0 ? columnGap : 0);
306
+ if (width + added > available && i > 0) return i;
307
+ width += added;
308
+ }
309
+ return columnWidths.length;
310
+ }
311
+ function autoLayout(data, options = {}) {
312
+ const opts = { ...DEFAULTS, ...options };
313
+ const nodeIds = Object.keys(data.nodes);
314
+ if (nodeIds.length === 0) return data;
315
+ const targetW = data.layout.width;
316
+ const targetH = data.layout.height;
317
+ const layerMap = assignLayers(nodeIds, data.connectors);
318
+ const layers = /* @__PURE__ */ new Map();
319
+ for (const [id, layer] of layerMap) {
320
+ if (!layers.has(layer)) layers.set(layer, []);
321
+ layers.get(layer).push(id);
322
+ }
323
+ const orderedLayers = orderWithinLayers(layers, data.connectors);
324
+ const layerCount = Math.max(...orderedLayers.keys()) + 1;
325
+ const columnWidths = [];
326
+ const columnNodes = [];
327
+ for (let l = 0; l < layerCount; l++) {
328
+ const nodes = orderedLayers.get(l) || [];
329
+ columnNodes.push(nodes);
330
+ let maxWidth = 0;
331
+ for (const id of nodes) {
332
+ const size = NODE_SIZES[data.nodes[id]?.size || "m"] || NODE_SIZES.m;
333
+ maxWidth = Math.max(maxWidth, size.width);
334
+ }
335
+ columnWidths.push(maxWidth);
336
+ }
337
+ const layersPerRow = fitLayersPerRow(columnWidths, targetW, opts.padding, opts.columnGap);
338
+ const numRows = Math.ceil(layerCount / layersPerRow);
339
+ const rowGroups = [];
340
+ for (let r = 0; r < numRows; r++) {
341
+ const start = r * layersPerRow;
342
+ const end = Math.min(start + layersPerRow, layerCount);
343
+ const group = [];
344
+ for (let l = start; l < end; l++) group.push(l);
345
+ rowGroups.push(group);
346
+ }
347
+ const newNodes = {};
348
+ let yOffset = opts.padding;
349
+ for (let r = 0; r < rowGroups.length; r++) {
350
+ const group = rowGroups[r];
351
+ const colGap = opts.columnGap;
352
+ let rowMaxHeight = 0;
353
+ for (const l of group) {
354
+ const nodes = columnNodes[l];
355
+ let colH = 0;
356
+ for (const id of nodes) {
357
+ const size = NODE_SIZES[data.nodes[id]?.size || "m"] || NODE_SIZES.m;
358
+ colH += size.height;
359
+ }
360
+ colH += Math.max(0, nodes.length - 1) * opts.nodeGap;
361
+ rowMaxHeight = Math.max(rowMaxHeight, colH);
362
+ }
363
+ let x = opts.padding;
364
+ for (const l of group) {
365
+ const nodes = columnNodes[l];
366
+ let colH = 0;
367
+ for (const id of nodes) {
368
+ const size = NODE_SIZES[data.nodes[id]?.size || "m"] || NODE_SIZES.m;
369
+ colH += size.height;
370
+ }
371
+ colH += Math.max(0, nodes.length - 1) * opts.nodeGap;
372
+ let y = yOffset + (rowMaxHeight - colH) / 2;
373
+ for (const id of nodes) {
374
+ const origNode = data.nodes[id];
375
+ const size = NODE_SIZES[origNode?.size || "m"] || NODE_SIZES.m;
376
+ const nodeX = x + (columnWidths[l] - size.width) / 2;
377
+ newNodes[id] = { x: nodeX, y, size: origNode?.size || "m" };
378
+ y += size.height + opts.nodeGap;
379
+ }
380
+ x += columnWidths[l] + colGap;
381
+ }
382
+ yOffset += rowMaxHeight + opts.rowGap;
383
+ }
384
+ const newConnectors = data.connectors.map((c) => {
385
+ const fromNode = newNodes[c.from];
386
+ const toNode = newNodes[c.to];
387
+ if (!fromNode || !toNode) return c;
388
+ const { fromAnchor, toAnchor } = inferAnchors(fromNode, toNode);
389
+ return { ...c, fromAnchor, toAnchor };
390
+ });
391
+ let maxX = 0;
392
+ let maxY = 0;
393
+ for (const node of Object.values(newNodes)) {
394
+ const size = NODE_SIZES[node.size] || NODE_SIZES.m;
395
+ maxX = Math.max(maxX, node.x + size.width);
396
+ maxY = Math.max(maxY, node.y + size.height);
397
+ }
398
+ return {
399
+ ...data,
400
+ layout: {
401
+ width: Math.max(targetW, maxX + opts.padding),
402
+ height: Math.max(targetH, maxY + opts.padding)
403
+ },
404
+ nodes: newNodes,
405
+ connectors: newConnectors
406
+ };
407
+ }
408
+ function createAutoLayout(input) {
409
+ const nodes = {};
410
+ for (const id of Object.keys(input.nodeData)) {
411
+ nodes[id] = { x: 0, y: 0, size: "m" };
412
+ }
413
+ const connectors = input.connectors.map((c) => ({
414
+ from: c.from,
415
+ to: c.to,
416
+ fromAnchor: c.fromAnchor || "right",
417
+ toAnchor: c.toAnchor || "left",
418
+ style: c.style,
419
+ ...c.curve ? { curve: c.curve } : {}
420
+ }));
421
+ const data = {
422
+ layout: { width: 800, height: 400 },
423
+ nodes,
424
+ nodeData: input.nodeData,
425
+ connectors,
426
+ connectorStyles: input.connectorStyles
427
+ };
428
+ return autoLayout(data, input.layout);
429
+ }
430
+ function resolveHoverEffects(input) {
431
+ if (input === false) return { enabled: false, dim: false, dimOpacity: 1, connectorDimOpacity: 1, lift: false, glow: false, highlightEdges: false };
432
+ const cfg = typeof input === "object" ? input : {};
433
+ const dimOpacity = cfg.dimOpacity ?? 0.45;
434
+ return {
435
+ enabled: true,
436
+ dim: cfg.dim ?? true,
437
+ dimOpacity,
438
+ connectorDimOpacity: dimOpacity * 0.56,
439
+ lift: cfg.lift ?? true,
440
+ glow: cfg.glow ?? true,
441
+ highlightEdges: cfg.highlightEdges ?? true
442
+ };
443
+ }
444
+ var NODE_SIZES2 = {
445
+ l: { width: 200, height: 68 },
446
+ m: { width: 140, height: 52 },
447
+ s: { width: 100, height: 40 },
448
+ xs: { width: 36, height: 52 }
449
+ };
450
+ function Node({ node, data, mode, themeColors, editable, onDragStart, hovered, dimmed, lift = true, glow = true, dimOpacity = 0.45, onMouseEnter, onMouseLeave, onClick }) {
451
+ const size = NODE_SIZES2[node.size];
452
+ const color = themeColors.palette[data.color] || themeColors.palette.zinc;
453
+ const Icon = LucideIcons[data.icon] || LucideIcons.Box;
454
+ const isLarge = node.size === "l";
455
+ const isSmall = node.size === "s";
456
+ const isXs = node.size === "xs";
457
+ const isLight = mode === "light";
458
+ const dragProps = editable ? {
459
+ onMouseDown: (e) => {
460
+ e.stopPropagation();
461
+ onDragStart?.(e);
462
+ }
463
+ } : {};
464
+ const editClass = editable ? "cursor-move hover:brightness-110" : "";
465
+ if (isXs) {
466
+ return /* @__PURE__ */ jsxs(
467
+ "div",
468
+ {
469
+ className: `absolute flex flex-col items-center transition-all duration-200 ease-out ${editClass}`,
470
+ style: {
471
+ left: node.x,
472
+ top: node.y,
473
+ width: size.width,
474
+ zIndex: hovered ? 10 : editable ? 1 : void 0,
475
+ transform: hovered && lift ? "translateY(-2px)" : "none",
476
+ opacity: dimmed ? dimOpacity : 1,
477
+ cursor: onClick ? "pointer" : void 0
478
+ },
479
+ "data-arc-node": true,
480
+ onMouseEnter,
481
+ onMouseLeave,
482
+ onClick,
483
+ ...dragProps,
484
+ children: [
485
+ /* @__PURE__ */ jsx("div", { className: `
486
+ rounded-full w-9 h-9 flex items-center justify-center
487
+ ${isLight ? "bg-white border border-zinc-200/80 shadow-sm" : "bg-zinc-800/90 border border-zinc-700/60"}
488
+ `, children: /* @__PURE__ */ jsx(Icon, { className: `w-4 h-4 ${color.icon}`, strokeWidth: 1.5 }) }),
489
+ data.name && /* @__PURE__ */ jsx(
490
+ "div",
491
+ {
492
+ className: `mt-1.5 text-center ${themeColors.text.muted} text-[8px] tracking-wide uppercase`,
493
+ style: { fontFamily: "ui-sans-serif, system-ui, sans-serif" },
494
+ children: data.name
495
+ }
496
+ )
497
+ ]
498
+ }
499
+ );
500
+ }
501
+ return /* @__PURE__ */ jsxs(
502
+ "div",
503
+ {
504
+ className: `
505
+ absolute rounded-lg border transition-all duration-200 ease-out
506
+ ${isLight ? "bg-white/90 border-zinc-200/70 shadow-sm" : "bg-zinc-900/80 border-zinc-700/50"}
507
+ ${editClass}
508
+ `,
509
+ style: {
510
+ left: node.x,
511
+ top: node.y,
512
+ width: size.width,
513
+ zIndex: hovered ? 10 : editable ? 1 : void 0,
514
+ backdropFilter: "blur(8px)",
515
+ transform: hovered && lift ? "translateY(-2px)" : "none",
516
+ boxShadow: hovered && glow ? `0 8px 24px -4px ${color.stroke}33, 0 0 0 1px ${color.stroke}22` : "none",
517
+ opacity: dimmed ? dimOpacity : 1,
518
+ cursor: onClick ? "pointer" : void 0
519
+ },
520
+ "data-arc-node": true,
521
+ onMouseEnter,
522
+ onMouseLeave,
523
+ onClick,
524
+ ...dragProps,
525
+ children: [
526
+ /* @__PURE__ */ jsx(
527
+ "div",
528
+ {
529
+ className: "absolute top-0 left-3 right-3 h-[2px] rounded-full",
530
+ style: { backgroundColor: color.stroke, opacity: 0.6 }
531
+ }
532
+ ),
533
+ /* @__PURE__ */ jsxs("div", { className: `${isLarge ? "px-4 py-3" : isSmall ? "px-2.5 py-1.5" : "px-3 py-2"}`, children: [
534
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
535
+ /* @__PURE__ */ jsx(
536
+ Icon,
537
+ {
538
+ className: `flex-shrink-0 ${isLarge ? "w-4 h-4" : isSmall ? "w-3 h-3" : "w-3.5 h-3.5"}`,
539
+ style: { color: color.stroke },
540
+ strokeWidth: 1.5
541
+ }
542
+ ),
543
+ /* @__PURE__ */ jsx("span", { className: `font-medium ${themeColors.text.primary} ${isLarge ? "text-[13px]" : isSmall ? "text-[10px]" : "text-[11px]"} leading-tight`, children: data.name })
544
+ ] }),
545
+ data.subtitle && /* @__PURE__ */ jsx(
546
+ "div",
547
+ {
548
+ className: `${themeColors.text.muted} ${isSmall ? "text-[8px] mt-0.5" : "text-[9px] mt-1"} leading-tight`,
549
+ style: { fontFamily: "ui-monospace, monospace" },
550
+ children: data.subtitle
551
+ }
552
+ ),
553
+ data.description && !isSmall && /* @__PURE__ */ jsx("div", { className: `mt-1 ${themeColors.text.secondary} text-[9px] leading-relaxed`, children: data.description })
554
+ ] })
555
+ ]
556
+ }
557
+ );
558
+ }
559
+ function getAnchorPoint(node, anchor) {
560
+ const size = NODE_SIZES2[node.size];
561
+ const gap = 6;
562
+ const anchors = {
563
+ left: { x: node.x - gap, y: node.y + size.height / 2 },
564
+ right: { x: node.x + size.width + gap, y: node.y + size.height / 2 },
565
+ top: { x: node.x + size.width / 2, y: node.y - gap },
566
+ bottom: { x: node.x + size.width / 2, y: node.y + size.height + gap },
567
+ bottomRight: { x: node.x + size.width + gap, y: node.y + size.height - 12 },
568
+ bottomLeft: { x: node.x - gap, y: node.y + size.height - 12 },
569
+ topRight: { x: node.x + size.width + gap, y: node.y + 12 },
570
+ topLeft: { x: node.x - gap, y: node.y + 12 }
571
+ };
572
+ return anchors[anchor];
573
+ }
574
+ function getAngle(from, to) {
575
+ return Math.atan2(to.y - from.y, to.x - from.x) * (180 / Math.PI);
576
+ }
577
+ function ConnectorPath({ connector, connectorIndex, nodes, styles, themeColors, highlighted, dimmed, dimOpacity = 0.25 }) {
578
+ const fromNode = nodes[connector.from];
579
+ const toNode = nodes[connector.to];
580
+ if (!fromNode || !toNode) return null;
581
+ const style = styles[connector.style] || { color: "zinc", strokeWidth: 2 };
582
+ const from = getAnchorPoint(fromNode, connector.fromAnchor);
583
+ const to = getAnchorPoint(toNode, connector.toAnchor);
584
+ const color = themeColors.palette[style.color]?.stroke || themeColors.palette.zinc.stroke;
585
+ const gradientId = `connector-gradient-${connectorIndex}`;
586
+ let path;
587
+ const isVertical = Math.abs(to.y - from.y) > Math.abs(to.x - from.x);
588
+ const labelAlign = style.labelAlign || (isVertical ? "right" : "center");
589
+ let labelPos;
590
+ let labelOffset = { x: 0, y: 0 };
591
+ let textAnchor = "middle";
592
+ const dx = to.x - from.x;
593
+ const dy = to.y - from.y;
594
+ if (connector.curve === "step") {
595
+ const midX = from.x + dx / 2;
596
+ path = `M ${from.x} ${from.y} L ${midX} ${from.y} L ${midX} ${to.y} L ${to.x} ${to.y}`;
597
+ } else {
598
+ const tension = 0.5;
599
+ if (isVertical) {
600
+ const cpy = dy * tension;
601
+ path = `M ${from.x} ${from.y} C ${from.x} ${from.y + cpy}, ${to.x} ${to.y - cpy}, ${to.x} ${to.y}`;
602
+ } else {
603
+ const cpx = dx * tension;
604
+ path = `M ${from.x} ${from.y} C ${from.x + cpx} ${from.y}, ${to.x - cpx} ${to.y}, ${to.x} ${to.y}`;
605
+ }
606
+ }
607
+ labelPos = { x: (from.x + to.x) / 2, y: (from.y + to.y) / 2 };
608
+ if (isVertical) {
609
+ if (labelAlign === "right") {
610
+ labelOffset = { x: 8, y: 4 };
611
+ textAnchor = "start";
612
+ } else if (labelAlign === "left") {
613
+ labelOffset = { x: -8, y: 4 };
614
+ textAnchor = "end";
615
+ } else {
616
+ labelOffset = { x: 0, y: -8 };
617
+ textAnchor = "middle";
618
+ }
619
+ } else {
620
+ labelOffset = { x: 0, y: -8 };
621
+ textAnchor = "middle";
622
+ }
623
+ const angle = getAngle(from, to);
624
+ const arrowSize = 6;
625
+ return /* @__PURE__ */ jsxs("g", { style: {
626
+ opacity: dimmed ? dimOpacity : 1,
627
+ transition: "opacity 200ms ease-out"
628
+ }, children: [
629
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
630
+ "linearGradient",
631
+ {
632
+ id: gradientId,
633
+ x1: from.x,
634
+ y1: from.y,
635
+ x2: to.x,
636
+ y2: to.y,
637
+ gradientUnits: "userSpaceOnUse",
638
+ children: [
639
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: color, stopOpacity: highlighted ? 0.6 : 0.4 }),
640
+ /* @__PURE__ */ jsx("stop", { offset: "10%", stopColor: color, stopOpacity: highlighted ? 0.9 : 0.7 }),
641
+ /* @__PURE__ */ jsx("stop", { offset: "90%", stopColor: color, stopOpacity: highlighted ? 0.9 : 0.7 }),
642
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: color, stopOpacity: highlighted ? 0.6 : 0.4 })
643
+ ]
644
+ }
645
+ ) }),
646
+ /* @__PURE__ */ jsx(
647
+ "path",
648
+ {
649
+ d: path,
650
+ fill: "none",
651
+ stroke: `url(#${gradientId})`,
652
+ strokeWidth: highlighted ? style.strokeWidth + 1 : style.strokeWidth,
653
+ strokeDasharray: style.dashed ? "6 3" : void 0,
654
+ style: { transition: "stroke-width 200ms ease-out" }
655
+ }
656
+ ),
657
+ /* @__PURE__ */ jsx("g", { transform: `translate(${to.x}, ${to.y}) rotate(${angle})`, children: /* @__PURE__ */ jsx(
658
+ "polygon",
659
+ {
660
+ points: `0,0 ${-arrowSize},-${arrowSize / 2.5} ${-arrowSize},${arrowSize / 2.5}`,
661
+ fill: color
662
+ }
663
+ ) }),
664
+ style.label && /* @__PURE__ */ jsxs("g", { style: { transition: "opacity 200ms ease-out" }, children: [
665
+ /* @__PURE__ */ jsx(
666
+ "rect",
667
+ {
668
+ x: labelPos.x + labelOffset.x - (textAnchor === "middle" ? style.label.length * 3.2 : textAnchor === "end" ? style.label.length * 6.4 : 0) - 4,
669
+ y: labelPos.y + labelOffset.y - 10,
670
+ width: style.label.length * 6.4 + 8,
671
+ height: 14,
672
+ rx: 4,
673
+ fill: color,
674
+ opacity: highlighted ? 0.3 : 0.15
675
+ }
676
+ ),
677
+ /* @__PURE__ */ jsx(
678
+ "text",
679
+ {
680
+ x: labelPos.x + labelOffset.x,
681
+ y: labelPos.y + labelOffset.y,
682
+ textAnchor,
683
+ fill: color,
684
+ className: "text-[9px] font-mono",
685
+ style: { fontFamily: "ui-monospace, monospace", fontWeight: highlighted ? 700 : 500 },
686
+ children: style.label
687
+ }
688
+ )
689
+ ] })
690
+ ] });
691
+ }
692
+ var ZOOM_LEVELS = [0.5, 0.75, 1, 1.25, 1.5, 2];
693
+ function ZoomControls({ zoom, onZoomIn, onZoomOut, onReset, mode }) {
694
+ const { ZoomIn, ZoomOut } = LucideIcons;
695
+ const isLight = mode === "light";
696
+ return /* @__PURE__ */ jsxs("div", { className: `absolute bottom-3 right-3 flex items-center backdrop-blur-sm rounded-md z-10 ${isLight ? "bg-white/90 border border-zinc-200 shadow-sm" : "bg-zinc-900/90 border border-zinc-700"}`, children: [
697
+ /* @__PURE__ */ jsx(
698
+ "button",
699
+ {
700
+ onClick: onZoomOut,
701
+ disabled: zoom <= ZOOM_LEVELS[0],
702
+ className: `p-1 disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-l-md ${isLight ? "hover:bg-zinc-100" : "hover:bg-zinc-700"}`,
703
+ title: "Zoom out",
704
+ children: /* @__PURE__ */ jsx(ZoomOut, { className: `w-3 h-3 ${isLight ? "text-zinc-500" : "text-zinc-400"}` })
705
+ }
706
+ ),
707
+ /* @__PURE__ */ jsxs(
708
+ "button",
709
+ {
710
+ onClick: onReset,
711
+ className: `px-1.5 py-1 text-[9px] font-mono transition-colors min-w-[36px] ${isLight ? "text-zinc-500 hover:bg-zinc-100 border-x border-zinc-200" : "text-zinc-400 hover:bg-zinc-700 border-x border-zinc-700"}`,
712
+ title: "Reset zoom",
713
+ children: [
714
+ Math.round(zoom * 100),
715
+ "%"
716
+ ]
717
+ }
718
+ ),
719
+ /* @__PURE__ */ jsx(
720
+ "button",
721
+ {
722
+ onClick: onZoomIn,
723
+ disabled: zoom >= ZOOM_LEVELS[ZOOM_LEVELS.length - 1],
724
+ className: `p-1 disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-r-md ${isLight ? "hover:bg-zinc-100" : "hover:bg-zinc-700"}`,
725
+ title: "Zoom in",
726
+ children: /* @__PURE__ */ jsx(ZoomIn, { className: `w-3 h-3 ${isLight ? "text-zinc-500" : "text-zinc-400"}` })
727
+ }
728
+ )
729
+ ] });
730
+ }
731
+ function generateSource(data) {
732
+ const clean = {
733
+ id: data.id,
734
+ layout: data.layout,
735
+ nodes: data.nodes,
736
+ nodeData: data.nodeData,
737
+ connectors: data.connectors,
738
+ connectorStyles: data.connectorStyles
739
+ };
740
+ const json = JSON.stringify(clean, null, 2).replace(/"([^"]+)":/g, "$1:").replace(/"/g, "'");
741
+ return `import type { ArcDiagramData } from '@arach/arc'
742
+
743
+ const diagram: ArcDiagramData = ${json}
744
+
745
+ export default diagram
746
+ `;
747
+ }
748
+ function ArcSourceView({ source, mode }) {
749
+ const isLight = mode === "light";
750
+ const [copied, setCopied] = useState(false);
751
+ const handleCopy = useCallback(() => {
752
+ navigator.clipboard.writeText(source).then(() => {
753
+ setCopied(true);
754
+ setTimeout(() => setCopied(false), 2e3);
755
+ });
756
+ }, [source]);
757
+ const { Copy, Check } = LucideIcons;
758
+ return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 overflow-auto z-[5]", style: {
759
+ backgroundColor: isLight ? "rgba(250,250,250,0.97)" : "rgba(9,9,11,0.97)"
760
+ }, children: [
761
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-1 px-4 pt-3", children: /* @__PURE__ */ jsx(
762
+ "button",
763
+ {
764
+ onClick: handleCopy,
765
+ className: `p-1 rounded transition-colors ${isLight ? "hover:bg-zinc-200/60 text-zinc-400" : "hover:bg-zinc-700/60 text-zinc-500"}`,
766
+ title: copied ? "Copied!" : "Copy",
767
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "w-3 h-3 text-emerald-500" }) : /* @__PURE__ */ jsx(Copy, { className: "w-3 h-3" })
768
+ }
769
+ ) }),
770
+ /* @__PURE__ */ jsx(
771
+ "pre",
772
+ {
773
+ className: `px-5 pb-5 pt-2 text-[11px] leading-relaxed font-mono whitespace-pre ${isLight ? "text-zinc-600" : "text-zinc-400"}`,
774
+ style: { fontFamily: '"JetBrains Mono", "Fira Code", "SF Mono", Consolas, monospace', tabSize: 2 },
775
+ children: source
776
+ }
777
+ )
778
+ ] });
779
+ }
780
+ function ViewToggle({ showArc, onToggle, mode }) {
781
+ const isLight = mode === "light";
782
+ const { Braces, Layers } = LucideIcons;
783
+ return /* @__PURE__ */ jsx(
784
+ "button",
785
+ {
786
+ onClick: onToggle,
787
+ className: `absolute top-3 right-3 flex items-center gap-1.5 px-2 py-1 rounded-md backdrop-blur-sm z-10 text-[10px] font-mono transition-colors ${isLight ? "bg-white/90 border border-zinc-200 shadow-sm text-zinc-500 hover:text-zinc-700 hover:bg-zinc-50" : "bg-zinc-900/90 border border-zinc-700 text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800"}`,
788
+ title: showArc ? "Back to diagram" : "View source",
789
+ children: showArc ? /* @__PURE__ */ jsxs(Fragment, { children: [
790
+ /* @__PURE__ */ jsx(Layers, { className: "w-3 h-3" }),
791
+ " Diagram"
792
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
793
+ /* @__PURE__ */ jsx(Braces, { className: "w-3 h-3" }),
794
+ " Source"
795
+ ] })
796
+ }
797
+ );
798
+ }
799
+ function AutoLayoutButton({ active, onToggle, mode }) {
800
+ const isLight = mode === "light";
801
+ const { Wand2 } = LucideIcons;
802
+ return /* @__PURE__ */ jsxs(
803
+ "button",
804
+ {
805
+ onClick: onToggle,
806
+ className: `flex items-center gap-1.5 px-2 py-1 rounded-md backdrop-blur-sm text-[10px] font-mono transition-colors ${active ? isLight ? "bg-violet-50 border border-violet-200 text-violet-600 shadow-sm" : "bg-violet-950/80 border border-violet-700 text-violet-400" : isLight ? "bg-white/90 border border-zinc-200 shadow-sm text-zinc-500 hover:text-zinc-700 hover:bg-zinc-50" : "bg-zinc-900/90 border border-zinc-700 text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800"}`,
807
+ title: active ? "Reset to original layout" : "Auto-layout nodes",
808
+ children: [
809
+ /* @__PURE__ */ jsx(Wand2, { className: "w-3 h-3" }),
810
+ " Auto"
811
+ ]
812
+ }
813
+ );
814
+ }
815
+ function EditButton({ url, mode }) {
816
+ const isLight = mode === "light";
817
+ const { Pencil } = LucideIcons;
818
+ return /* @__PURE__ */ jsxs(
819
+ "a",
820
+ {
821
+ href: url,
822
+ target: "_blank",
823
+ rel: "noopener noreferrer",
824
+ className: `flex items-center gap-1.5 px-2 py-1 rounded-md backdrop-blur-sm text-[10px] font-mono transition-colors no-underline ${isLight ? "bg-white/90 border border-zinc-200 shadow-sm text-zinc-500 hover:text-zinc-700 hover:bg-zinc-50" : "bg-zinc-900/90 border border-zinc-700 text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800"}`,
825
+ title: "Open in Arc editor",
826
+ children: [
827
+ /* @__PURE__ */ jsx(Pencil, { className: "w-3 h-3" }),
828
+ " Edit"
829
+ ]
830
+ }
831
+ );
832
+ }
833
+ function ArcDiagram({ data, className = "", interactive = true, mode = "dark", theme = "default", showArcToggle = true, showAutoLayout = false, editable = false, editorUrl, editorMeta, hoverEffects, onNodeHover }) {
834
+ const isLight = mode === "light";
835
+ const [showArc, setShowArc] = useState(false);
836
+ const [useAutoLayout, setUseAutoLayout] = useState(false);
837
+ const [hoveredNodeId, setHoveredNodeId] = useState(null);
838
+ const [lockedNodeId, setLockedNodeId] = useState(null);
839
+ const fx = useMemo(() => resolveHoverEffects(hoverEffects), [hoverEffects]);
840
+ const activeNodeId = fx.enabled ? lockedNodeId ?? hoveredNodeId : null;
841
+ const handleNodeClick = useCallback((nodeId) => {
842
+ setLockedNodeId((prev) => {
843
+ const next = prev === nodeId ? null : nodeId;
844
+ onNodeHover?.(next);
845
+ return next;
846
+ });
847
+ }, [onNodeHover]);
848
+ const baseData = useMemo(
849
+ () => useAutoLayout ? autoLayout(data) : data,
850
+ [data, useAutoLayout]
851
+ );
852
+ const storageKey = editable && data.id ? `arc-drag-${data.id}` : null;
853
+ const [draggedPositions, setDraggedPositions] = useState(() => {
854
+ if (!storageKey) return {};
855
+ try {
856
+ const saved = localStorage.getItem(storageKey);
857
+ return saved ? JSON.parse(saved) : {};
858
+ } catch {
859
+ return {};
860
+ }
861
+ });
862
+ const [draggingNode, setDraggingNode] = useState(null);
863
+ React.useEffect(() => {
864
+ if (!storageKey) return;
865
+ if (Object.keys(draggedPositions).length === 0) {
866
+ localStorage.removeItem(storageKey);
867
+ } else {
868
+ localStorage.setItem(storageKey, JSON.stringify(draggedPositions));
869
+ }
870
+ }, [draggedPositions, storageKey]);
871
+ React.useEffect(() => {
872
+ setDraggedPositions({});
873
+ }, [useAutoLayout]);
874
+ const activeData = useMemo(() => {
875
+ if (Object.keys(draggedPositions).length === 0) return baseData;
876
+ const mergedNodes = { ...baseData.nodes };
877
+ for (const [id2, pos] of Object.entries(draggedPositions)) {
878
+ if (mergedNodes[id2]) {
879
+ mergedNodes[id2] = { ...mergedNodes[id2], x: pos.x, y: pos.y };
880
+ }
881
+ }
882
+ return { ...baseData, nodes: mergedNodes };
883
+ }, [baseData, draggedPositions]);
884
+ const { id, layout, nodes, nodeData, connectors, connectorStyles } = activeData;
885
+ const themeData = getTheme(theme);
886
+ const themeColors = isLight ? themeData.light : themeData.dark;
887
+ const sourceCode = useMemo(() => generateSource(activeData), [activeData]);
888
+ const [zoom, setZoom] = useState(1);
889
+ const [pan, setPan] = useState({ x: 0, y: 0 });
890
+ const [isPanning, setIsPanning] = useState(false);
891
+ const [panStart, setPanStart] = useState({ x: 0, y: 0 });
892
+ const handleZoomIn = useCallback(() => {
893
+ setZoom((z) => {
894
+ const idx = ZOOM_LEVELS.findIndex((l) => l >= z);
895
+ return ZOOM_LEVELS[Math.min(idx + 1, ZOOM_LEVELS.length - 1)];
896
+ });
897
+ }, []);
898
+ const handleZoomOut = useCallback(() => {
899
+ setZoom((z) => {
900
+ const idx = ZOOM_LEVELS.findIndex((l) => l >= z);
901
+ return ZOOM_LEVELS[Math.max(idx - 1, 0)];
902
+ });
903
+ }, []);
904
+ const handleReset = useCallback(() => {
905
+ setZoom(1);
906
+ setPan({ x: 0, y: 0 });
907
+ }, []);
908
+ const handleWheel = useCallback((e) => {
909
+ if (!interactive) return;
910
+ if (e.ctrlKey || e.metaKey) {
911
+ e.preventDefault();
912
+ if (e.deltaY < 0) handleZoomIn();
913
+ else handleZoomOut();
914
+ }
915
+ }, [interactive, handleZoomIn, handleZoomOut]);
916
+ const handleMouseDown = useCallback((e) => {
917
+ if (!interactive || draggingNode) return;
918
+ if (lockedNodeId && e.target.closest("[data-arc-node]") === null) {
919
+ setLockedNodeId(null);
920
+ onNodeHover?.(null);
921
+ }
922
+ setIsPanning(true);
923
+ setPanStart({ x: e.clientX - pan.x, y: e.clientY - pan.y });
924
+ }, [interactive, pan, draggingNode, lockedNodeId, onNodeHover]);
925
+ const handleMouseMove = useCallback((e) => {
926
+ if (draggingNode) {
927
+ const dx = (e.clientX - draggingNode.startX) / zoom;
928
+ const dy = (e.clientY - draggingNode.startY) / zoom;
929
+ setDraggedPositions((prev) => ({
930
+ ...prev,
931
+ [draggingNode.id]: {
932
+ x: Math.round(draggingNode.nodeX + dx),
933
+ y: Math.round(draggingNode.nodeY + dy)
934
+ }
935
+ }));
936
+ return;
937
+ }
938
+ if (!isPanning) return;
939
+ setPan({
940
+ x: e.clientX - panStart.x,
941
+ y: e.clientY - panStart.y
942
+ });
943
+ }, [isPanning, panStart, draggingNode, zoom]);
944
+ const handleMouseUp = useCallback(() => {
945
+ setIsPanning(false);
946
+ setDraggingNode(null);
947
+ }, []);
948
+ return /* @__PURE__ */ jsxs(
949
+ "div",
950
+ {
951
+ className: `rounded-2xl overflow-hidden relative ${themeColors.background.container} ${className}`,
952
+ onWheel: handleWheel,
953
+ onMouseDown: handleMouseDown,
954
+ onMouseMove: handleMouseMove,
955
+ onMouseUp: handleMouseUp,
956
+ onMouseLeave: handleMouseUp,
957
+ style: { cursor: draggingNode ? "grabbing" : interactive ? isPanning ? "grabbing" : "grab" : "default" },
958
+ children: [
959
+ /* @__PURE__ */ jsxs(
960
+ "div",
961
+ {
962
+ className: "relative transition-transform duration-150 ease-out",
963
+ style: {
964
+ width: layout.width,
965
+ height: layout.height,
966
+ minWidth: layout.width,
967
+ transform: `scale(${zoom}) translate(${pan.x / zoom}px, ${pan.y / zoom}px)`,
968
+ transformOrigin: "top left"
969
+ },
970
+ children: [
971
+ /* @__PURE__ */ jsx(
972
+ "div",
973
+ {
974
+ className: "absolute",
975
+ style: {
976
+ top: -2e3,
977
+ left: -2e3,
978
+ width: layout.width + 4e3,
979
+ height: layout.height + 4e3,
980
+ backgroundImage: `radial-gradient(circle, ${themeColors.background.grid.color} 1px, transparent 1px)`,
981
+ backgroundSize: `${themeColors.background.grid.size}px ${themeColors.background.grid.size}px`,
982
+ opacity: themeColors.background.grid.opacity
983
+ }
984
+ }
985
+ ),
986
+ /* @__PURE__ */ jsx(
987
+ "svg",
988
+ {
989
+ className: "absolute inset-0 w-full h-full pointer-events-none",
990
+ viewBox: `0 0 ${layout.width} ${layout.height}`,
991
+ children: connectors.map((conn, i) => {
992
+ const isConnected = activeNodeId != null && (conn.from === activeNodeId || conn.to === activeNodeId);
993
+ return /* @__PURE__ */ jsx(
994
+ ConnectorPath,
995
+ {
996
+ connector: conn,
997
+ connectorIndex: i,
998
+ nodes,
999
+ styles: connectorStyles,
1000
+ themeColors,
1001
+ highlighted: fx.highlightEdges && isConnected,
1002
+ dimmed: fx.dim && activeNodeId != null && !isConnected,
1003
+ dimOpacity: fx.connectorDimOpacity
1004
+ },
1005
+ i
1006
+ );
1007
+ })
1008
+ }
1009
+ ),
1010
+ Object.entries(nodes).map(([nodeId, node]) => {
1011
+ const isActive = activeNodeId === nodeId;
1012
+ return /* @__PURE__ */ jsx(
1013
+ Node,
1014
+ {
1015
+ node,
1016
+ data: nodeData[nodeId],
1017
+ mode,
1018
+ themeColors,
1019
+ editable,
1020
+ onDragStart: editable ? (e) => {
1021
+ setDraggingNode({ id: nodeId, startX: e.clientX, startY: e.clientY, nodeX: node.x, nodeY: node.y });
1022
+ } : void 0,
1023
+ hovered: isActive,
1024
+ dimmed: fx.dim && activeNodeId != null && !isActive,
1025
+ lift: fx.lift,
1026
+ glow: fx.glow,
1027
+ dimOpacity: fx.dimOpacity,
1028
+ onMouseEnter: () => {
1029
+ if (!lockedNodeId) {
1030
+ setHoveredNodeId(nodeId);
1031
+ onNodeHover?.(nodeId);
1032
+ }
1033
+ },
1034
+ onMouseLeave: () => {
1035
+ if (!lockedNodeId) {
1036
+ setHoveredNodeId(null);
1037
+ onNodeHover?.(null);
1038
+ }
1039
+ },
1040
+ onClick: () => handleNodeClick(nodeId)
1041
+ },
1042
+ nodeId
1043
+ );
1044
+ })
1045
+ ]
1046
+ }
1047
+ ),
1048
+ showArc && /* @__PURE__ */ jsx(ArcSourceView, { source: sourceCode, mode }),
1049
+ showArcToggle && /* @__PURE__ */ jsx(
1050
+ ViewToggle,
1051
+ {
1052
+ showArc,
1053
+ onToggle: () => setShowArc((s) => !s),
1054
+ mode
1055
+ }
1056
+ ),
1057
+ !showArc && /* @__PURE__ */ jsxs("div", { className: "absolute top-3 left-3 flex items-center gap-1.5 z-10", children: [
1058
+ showAutoLayout && /* @__PURE__ */ jsx(
1059
+ AutoLayoutButton,
1060
+ {
1061
+ active: useAutoLayout,
1062
+ onToggle: () => setUseAutoLayout((v) => !v),
1063
+ mode
1064
+ }
1065
+ ),
1066
+ editable && editorUrl && /* @__PURE__ */ jsx(EditButton, { url: `${editorUrl}#data=${btoa(JSON.stringify({ ...activeData, _viewport: editorMeta?.viewport, _theme: editorMeta?.theme, _mode: editorMeta?.mode }))}`, mode })
1067
+ ] }),
1068
+ id && /* @__PURE__ */ jsx("div", { className: `absolute bottom-3 left-3 font-mono text-[9px] tracking-wider z-10 ${themeColors.text.muted}`, children: id }),
1069
+ interactive && !showArc && /* @__PURE__ */ jsx(
1070
+ ZoomControls,
1071
+ {
1072
+ zoom,
1073
+ onZoomIn: handleZoomIn,
1074
+ onZoomOut: handleZoomOut,
1075
+ onReset: handleReset,
1076
+ mode
1077
+ }
1078
+ )
1079
+ ]
1080
+ }
1081
+ );
1082
+ }
1083
+ var ArcDiagram_default = ArcDiagram;
1084
+
1085
+ // src/mermaid/sequence/parseSequence.ts
1086
+ var SUPPORTED_FRAGMENTS = /* @__PURE__ */ new Set([
1087
+ "alt",
1088
+ "else",
1089
+ "opt",
1090
+ "loop",
1091
+ "par",
1092
+ "critical",
1093
+ "break",
1094
+ "rect"
1095
+ ]);
1096
+ var UNSUPPORTED_LINE_START = /^(activate|deactivate|autonumber|box|create|destroy)\b/i;
1097
+ function range(line) {
1098
+ return { startLine: line, endLine: line };
1099
+ }
1100
+ function sanitizeId(raw) {
1101
+ return raw.replace(/[^a-zA-Z0-9_-]/g, "_");
1102
+ }
1103
+ function splitStructuredText(raw) {
1104
+ return raw.split(/<br\s*\/?\s*>/gi).map((p) => p.trim()).filter((p) => p.length > 0);
1105
+ }
1106
+ function stripQuotes(s) {
1107
+ const t = s.trim();
1108
+ if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
1109
+ return t.slice(1, -1);
1110
+ }
1111
+ return t;
1112
+ }
1113
+ function parseSequenceSource(source, baseLine = 1) {
1114
+ const diagnostics = [];
1115
+ const lines = source.replace(/\r\n?/g, "\n").split("\n");
1116
+ const declaration = lines[0]?.trim() || "sequenceDiagram";
1117
+ const participants = [];
1118
+ const participantIndex2 = /* @__PURE__ */ new Map();
1119
+ const events = [];
1120
+ let order = 0;
1121
+ let eventSeq = 0;
1122
+ let fragmentSeq = 0;
1123
+ const fragStack = [];
1124
+ const ensureParticipant = (rawId, label, kind, lineNo) => {
1125
+ const existingIndex = participantIndex2.get(rawId);
1126
+ if (existingIndex !== void 0) {
1127
+ const existing = participants[existingIndex];
1128
+ if (label && existing && (existing.label === rawId || existing.label === existing.id)) {
1129
+ existing.label = label;
1130
+ }
1131
+ if (existing && kind === "actor") existing.kind = "actor";
1132
+ return existing?.id ?? sanitizeId(rawId);
1133
+ }
1134
+ const baseId = sanitizeId(rawId) || `participant_${participants.length + 1}`;
1135
+ let id = baseId;
1136
+ if (participants.some((p2) => p2.id === baseId)) {
1137
+ diagnostics.push({
1138
+ severity: "error",
1139
+ code: "id-collision",
1140
+ message: `Participant id collision after sanitization: "${rawId}" \u2192 "${baseId}"`,
1141
+ range: range(lineNo)
1142
+ });
1143
+ let suffix = 2;
1144
+ while (participants.some((p2) => p2.id === `${baseId}_${suffix}`)) {
1145
+ suffix += 1;
1146
+ }
1147
+ id = `${baseId}_${suffix}`;
1148
+ }
1149
+ const p = {
1150
+ id,
1151
+ label: label ?? rawId,
1152
+ kind,
1153
+ order: participants.length,
1154
+ range: range(lineNo)
1155
+ };
1156
+ participantIndex2.set(rawId, participants.length);
1157
+ participants.push(p);
1158
+ return id;
1159
+ };
1160
+ for (let i = 1; i < lines.length; i++) {
1161
+ const lineNo = baseLine + i;
1162
+ const rawLine = lines[i];
1163
+ const line = rawLine.trim();
1164
+ if (!line || line.startsWith("%%")) continue;
1165
+ const pMatch = line.match(
1166
+ /^(participant|actor)\s+(\S+)(?:\s+as\s+(.+))?$/i
1167
+ );
1168
+ if (pMatch) {
1169
+ const kind = pMatch[1].toLowerCase();
1170
+ const rawId = pMatch[2];
1171
+ const alias = pMatch[3] ? stripQuotes(pMatch[3]) : void 0;
1172
+ ensureParticipant(rawId, alias, kind, lineNo);
1173
+ continue;
1174
+ }
1175
+ const noteMatch = line.match(
1176
+ /^Note\s+(left of|right of|over)\s+([^:]+)\s*:\s*(.+)$/i
1177
+ );
1178
+ if (noteMatch) {
1179
+ const placementRaw = noteMatch[1].toLowerCase();
1180
+ const placement = placementRaw === "left of" ? "left" : placementRaw === "right of" ? "right" : "over";
1181
+ const targetRaw = noteMatch[2].trim();
1182
+ const text = splitStructuredText(noteMatch[3]);
1183
+ const targetIds = targetRaw.split(/\s*,\s*/).map((t) => {
1184
+ const cleaned = stripQuotes(t.trim());
1185
+ return ensureParticipant(cleaned, void 0, "participant", lineNo);
1186
+ });
1187
+ events.push({
1188
+ type: "note",
1189
+ id: `note-${++eventSeq}`,
1190
+ placement,
1191
+ participants: targetIds,
1192
+ text: text.length ? text : [noteMatch[3].trim()],
1193
+ order: order++,
1194
+ range: range(lineNo)
1195
+ });
1196
+ continue;
1197
+ }
1198
+ const msgMatch = line.match(
1199
+ /^(\S+?)\s*(-->>|->>|-->|->|--\)|-\)|--x|-x)\s*([+-])?\s*(\S+?)\s*:\s*(.+)$/
1200
+ );
1201
+ if (msgMatch) {
1202
+ const fromRaw = msgMatch[1];
1203
+ const arrowTok = msgMatch[2];
1204
+ const actMark = msgMatch[3];
1205
+ const toRaw = msgMatch[4];
1206
+ const labelRaw = msgMatch[5];
1207
+ const from = ensureParticipant(fromRaw, void 0, "participant", lineNo);
1208
+ const to = ensureParticipant(toRaw, void 0, "participant", lineNo);
1209
+ const dashed = arrowTok.startsWith("--");
1210
+ const open = arrowTok.endsWith(")") || arrowTok.endsWith("x");
1211
+ events.push({
1212
+ type: "message",
1213
+ id: `msg-${++eventSeq}`,
1214
+ from,
1215
+ to,
1216
+ label: splitStructuredText(labelRaw).length ? splitStructuredText(labelRaw) : [labelRaw.trim()],
1217
+ line: dashed ? "dashed" : "solid",
1218
+ arrow: open ? "open" : "filled",
1219
+ activateTarget: actMark === "+",
1220
+ deactivateTarget: actMark === "-",
1221
+ order: order++,
1222
+ range: range(lineNo)
1223
+ });
1224
+ continue;
1225
+ }
1226
+ const fragMatch = line.match(
1227
+ /^(alt|else|opt|loop|par|critical|break|rect)(?:\s+(.*))?$/i
1228
+ );
1229
+ if (fragMatch) {
1230
+ const keyword = fragMatch[1].toLowerCase();
1231
+ const label = fragMatch[2]?.trim() || void 0;
1232
+ if (keyword === "else") {
1233
+ const parent = fragStack[fragStack.length - 1];
1234
+ if (!parent) {
1235
+ diagnostics.push({
1236
+ severity: "error",
1237
+ code: "orphan-else",
1238
+ message: "`else` without matching fragment",
1239
+ range: range(lineNo)
1240
+ });
1241
+ continue;
1242
+ }
1243
+ events.push({
1244
+ type: "fragmentElse",
1245
+ id: `frag-else-${++fragmentSeq}`,
1246
+ fragmentId: parent.id,
1247
+ label,
1248
+ depth: parent.depth,
1249
+ order: order++,
1250
+ range: range(lineNo)
1251
+ });
1252
+ continue;
1253
+ }
1254
+ const kind = keyword;
1255
+ if (!SUPPORTED_FRAGMENTS.has(keyword)) {
1256
+ diagnostics.push({
1257
+ severity: "unsupported",
1258
+ code: "unsupported-fragment",
1259
+ message: `Fragment kind "${keyword}" is not rendered yet`,
1260
+ capability: keyword,
1261
+ range: range(lineNo)
1262
+ });
1263
+ }
1264
+ if (kind !== "alt" && kind !== "opt" && kind !== "loop") {
1265
+ diagnostics.push({
1266
+ severity: "unsupported",
1267
+ code: "partial-fragment",
1268
+ message: `Fragment "${kind}" is parsed but has limited visual treatment`,
1269
+ capability: kind,
1270
+ range: range(lineNo)
1271
+ });
1272
+ }
1273
+ const id = `frag-${++fragmentSeq}`;
1274
+ const depth = fragStack.length;
1275
+ fragStack.push({ id, kind, depth });
1276
+ events.push({
1277
+ type: "fragmentStart",
1278
+ id,
1279
+ kind,
1280
+ label,
1281
+ depth,
1282
+ order: order++,
1283
+ range: range(lineNo)
1284
+ });
1285
+ continue;
1286
+ }
1287
+ if (/^end$/i.test(line)) {
1288
+ const frame = fragStack.pop();
1289
+ if (!frame) {
1290
+ diagnostics.push({
1291
+ severity: "warning",
1292
+ code: "orphan-end",
1293
+ message: "`end` without matching fragment",
1294
+ range: range(lineNo)
1295
+ });
1296
+ continue;
1297
+ }
1298
+ events.push({
1299
+ type: "fragmentEnd",
1300
+ id: `frag-end-${++fragmentSeq}`,
1301
+ fragmentId: frame.id,
1302
+ depth: frame.depth,
1303
+ order: order++,
1304
+ range: range(lineNo)
1305
+ });
1306
+ continue;
1307
+ }
1308
+ if (UNSUPPORTED_LINE_START.test(line)) {
1309
+ const cap = line.split(/\s/)[0].toLowerCase();
1310
+ diagnostics.push({
1311
+ severity: "unsupported",
1312
+ code: "unsupported-construct",
1313
+ message: `Unsupported sequence construct: ${cap}`,
1314
+ capability: cap,
1315
+ range: range(lineNo)
1316
+ });
1317
+ continue;
1318
+ }
1319
+ diagnostics.push({
1320
+ severity: "warning",
1321
+ code: "unrecognized-line",
1322
+ message: `Skipped unrecognized line: "${line.substring(0, 80)}"`,
1323
+ range: range(lineNo)
1324
+ });
1325
+ }
1326
+ while (fragStack.length) {
1327
+ const frame = fragStack.pop();
1328
+ diagnostics.push({
1329
+ severity: "error",
1330
+ code: "unclosed-fragment",
1331
+ message: `Unclosed fragment "${frame.kind}" (${frame.id})`
1332
+ });
1333
+ }
1334
+ return {
1335
+ document: {
1336
+ family: "sequence",
1337
+ declaration,
1338
+ participants,
1339
+ events
1340
+ },
1341
+ diagnostics
1342
+ };
1343
+ }
1344
+
1345
+ // src/mermaid/parseMermaid.ts
1346
+ function normalizeSource(source) {
1347
+ return source.replace(/\r\n?/g, "\n");
1348
+ }
1349
+ function findDeclaration(lines) {
1350
+ return lines.findIndex((line) => {
1351
+ const trimmed = line.trim();
1352
+ return trimmed.length > 0 && !trimmed.startsWith("%%");
1353
+ });
1354
+ }
1355
+ function parseMermaid(source) {
1356
+ const normalized = normalizeSource(source);
1357
+ const lines = normalized.split("\n");
1358
+ const declarationIndex = findDeclaration(lines);
1359
+ if (declarationIndex === -1) {
1360
+ return {
1361
+ document: null,
1362
+ diagnostics: [
1363
+ {
1364
+ severity: "error",
1365
+ code: "no-declaration",
1366
+ message: "Mermaid source does not contain a diagram declaration"
1367
+ }
1368
+ ],
1369
+ source: normalized
1370
+ };
1371
+ }
1372
+ const body = lines.slice(declarationIndex).join("\n").trim();
1373
+ const firstLine = body.split("\n")[0].trim();
1374
+ const baseLine = declarationIndex + 1;
1375
+ const diagnostics = [];
1376
+ if (/^sequenceDiagram\b/i.test(firstLine)) {
1377
+ const result = parseSequenceSource(body, baseLine);
1378
+ return {
1379
+ document: result.document,
1380
+ diagnostics: result.diagnostics,
1381
+ source: normalized
1382
+ };
1383
+ }
1384
+ if (/^flowchart\b/i.test(firstLine) || /^graph\b/i.test(firstLine)) {
1385
+ const document = {
1386
+ family: "flowchart",
1387
+ declaration: firstLine.split(/\s/)[0],
1388
+ raw: body
1389
+ };
1390
+ diagnostics.push({
1391
+ severity: "unsupported",
1392
+ code: "native-renderer-pending",
1393
+ message: "Flowchart native rendering is not available yet",
1394
+ capability: "flowchart-native-renderer"
1395
+ });
1396
+ return { document, diagnostics, source: normalized };
1397
+ }
1398
+ if (/^stateDiagram-v2\b/i.test(firstLine) || /^stateDiagram\b/i.test(firstLine)) {
1399
+ const document = {
1400
+ family: "state",
1401
+ declaration: firstLine.split(/\s/)[0],
1402
+ raw: body
1403
+ };
1404
+ diagnostics.push({
1405
+ severity: "unsupported",
1406
+ code: "native-renderer-pending",
1407
+ message: "State diagram native rendering is not available yet",
1408
+ capability: "state-native-renderer"
1409
+ });
1410
+ return { document, diagnostics, source: normalized };
1411
+ }
1412
+ return {
1413
+ document: null,
1414
+ diagnostics: [
1415
+ {
1416
+ severity: "error",
1417
+ code: "unrecognized-type",
1418
+ message: `Unrecognized diagram type: "${firstLine}"`,
1419
+ capability: firstLine.split(/\s/)[0]
1420
+ }
1421
+ ],
1422
+ source: normalized
1423
+ };
1424
+ }
1425
+
1426
+ // src/mermaid/sequence/layout.ts
1427
+ var DEFAULT_WIDTH = 960;
1428
+ var PADDING_X = 22;
1429
+ var PADDING_Y = 16;
1430
+ var HEADER_HEIGHT = 46;
1431
+ var HEADER_BOX_W = 102;
1432
+ var HEADER_BOX_H = 34;
1433
+ var MIN_PARTICIPANT_GAP = 112;
1434
+ var ROW_GAP = 7;
1435
+ var MESSAGE_MIN_HEIGHT = 24;
1436
+ var NOTE_PAD_Y = 8;
1437
+ var NOTE_PAD_X = 10;
1438
+ var FRAGMENT_PAD = 9;
1439
+ var FRAGMENT_LABEL_H = 18;
1440
+ var LANE_SEP = 6;
1441
+ var CHAR_WIDTH = 6.8;
1442
+ var LINE_HEIGHT = 14;
1443
+ function estimateTextWidth(text, maxWidth) {
1444
+ if (!text) return { lines: [""], height: LINE_HEIGHT, width: 0 };
1445
+ const words = text.split(/\s+/);
1446
+ const lines = [];
1447
+ let current = "";
1448
+ for (const word of words) {
1449
+ const next = current ? `${current} ${word}` : word;
1450
+ if (next.length * CHAR_WIDTH > maxWidth && current) {
1451
+ lines.push(current);
1452
+ current = word;
1453
+ } else {
1454
+ current = next;
1455
+ }
1456
+ }
1457
+ if (current) lines.push(current);
1458
+ if (lines.length === 0) lines.push("");
1459
+ const width = Math.min(
1460
+ maxWidth,
1461
+ Math.max(...lines.map((l) => l.length * CHAR_WIDTH))
1462
+ );
1463
+ return { lines, height: lines.length * LINE_HEIGHT, width };
1464
+ }
1465
+ function estimateLabelBlock(label, maxWidth) {
1466
+ if (label.length === 0) return { height: LINE_HEIGHT, width: 0, lines: [""] };
1467
+ const allLines = [];
1468
+ let maxW = 0;
1469
+ let totalH = 0;
1470
+ for (const segment of label) {
1471
+ const est = estimateTextWidth(segment, maxWidth);
1472
+ allLines.push(...est.lines);
1473
+ maxW = Math.max(maxW, est.width);
1474
+ totalH += est.height;
1475
+ }
1476
+ return { height: Math.max(LINE_HEIGHT, totalH), width: maxW, lines: allLines };
1477
+ }
1478
+ function participantIndex(doc, id) {
1479
+ const idx = doc.participants.findIndex((p) => p.id === id);
1480
+ return idx >= 0 ? idx : 0;
1481
+ }
1482
+ function spanX(participantX, ids, boxHalf = HEADER_BOX_W / 2) {
1483
+ if (ids.length === 0) {
1484
+ const xs2 = Object.values(participantX);
1485
+ const min2 = Math.min(...xs2);
1486
+ const max2 = Math.max(...xs2);
1487
+ return { x: min2 - boxHalf, width: max2 - min2 + boxHalf * 2 };
1488
+ }
1489
+ const xs = ids.map((id) => participantX[id] ?? 0);
1490
+ const min = Math.min(...xs);
1491
+ const max = Math.max(...xs);
1492
+ return { x: min - boxHalf, width: Math.max(boxHalf * 2, max - min + boxHalf * 2) };
1493
+ }
1494
+ function layoutSequence(document, options = {}) {
1495
+ const targetWidth = options.width ?? DEFAULT_WIDTH;
1496
+ const maxLabelWidth = options.maxLabelWidth ?? 160;
1497
+ const n = document.participants.length || 1;
1498
+ const usable = targetWidth - PADDING_X * 2;
1499
+ const gap = Math.max(MIN_PARTICIPANT_GAP, usable / Math.max(n, 1));
1500
+ const contentWidth = Math.max(targetWidth, PADDING_X * 2 + gap * (n - 1) + HEADER_BOX_W);
1501
+ const startX = PADDING_X + HEADER_BOX_W / 2 + Math.max(0, (contentWidth - PADDING_X * 2 - gap * (n - 1) - HEADER_BOX_W) / 2);
1502
+ const participantX = {};
1503
+ document.participants.forEach((p, i) => {
1504
+ participantX[p.id] = startX + i * gap;
1505
+ });
1506
+ const rows = [];
1507
+ for (const event of document.events) {
1508
+ if (event.type === "message") {
1509
+ const est = estimateLabelBlock(event.label, maxLabelWidth);
1510
+ const self = event.from === event.to;
1511
+ const h = Math.max(MESSAGE_MIN_HEIGHT, est.height + (self ? 24 : 12)) + ROW_GAP;
1512
+ rows.push({ kind: "message", event, height: h, order: event.order });
1513
+ } else if (event.type === "note") {
1514
+ const est = estimateLabelBlock(event.text, maxLabelWidth + 40);
1515
+ const h = Math.max(36, est.height + NOTE_PAD_Y * 2) + ROW_GAP;
1516
+ rows.push({ kind: "note", event, height: h, order: event.order });
1517
+ } else if (event.type === "fragmentStart") {
1518
+ rows.push({
1519
+ kind: "fragmentStart",
1520
+ event,
1521
+ height: FRAGMENT_LABEL_H + FRAGMENT_PAD,
1522
+ order: event.order
1523
+ });
1524
+ } else if (event.type === "fragmentElse") {
1525
+ rows.push({
1526
+ kind: "fragmentElse",
1527
+ event,
1528
+ height: FRAGMENT_LABEL_H + LANE_SEP,
1529
+ order: event.order
1530
+ });
1531
+ } else if (event.type === "fragmentEnd") {
1532
+ rows.push({
1533
+ kind: "fragmentEnd",
1534
+ event,
1535
+ height: FRAGMENT_PAD,
1536
+ order: event.order
1537
+ });
1538
+ }
1539
+ }
1540
+ const headerY = PADDING_Y;
1541
+ let y = headerY + HEADER_HEIGHT + 12;
1542
+ const rowTops = [];
1543
+ const rowHeights = [];
1544
+ const orderY = /* @__PURE__ */ new Map();
1545
+ const orderH = /* @__PURE__ */ new Map();
1546
+ for (const row of rows) {
1547
+ rowTops.push(y);
1548
+ rowHeights.push(row.height);
1549
+ orderY.set(row.order, y);
1550
+ orderH.set(row.order, row.height);
1551
+ y += row.height;
1552
+ }
1553
+ const contentBottom = y + PADDING_Y;
1554
+ const metrics = {
1555
+ width: contentWidth,
1556
+ height: contentBottom,
1557
+ paddingX: PADDING_X,
1558
+ paddingY: PADDING_Y,
1559
+ headerHeight: HEADER_HEIGHT,
1560
+ participantGap: gap,
1561
+ participantX,
1562
+ rowTops,
1563
+ rowHeights
1564
+ };
1565
+ const messages = [];
1566
+ const notes = [];
1567
+ for (const row of rows) {
1568
+ if (row.kind === "message" && row.event?.type === "message") {
1569
+ const ev = row.event;
1570
+ const fromX = participantX[ev.from] ?? startX;
1571
+ const toX = participantX[ev.to] ?? startX;
1572
+ const top = orderY.get(ev.order) ?? 0;
1573
+ const h = orderH.get(ev.order) ?? MESSAGE_MIN_HEIGHT;
1574
+ messages.push({
1575
+ event: ev,
1576
+ y: top + h / 2 - ROW_GAP / 2,
1577
+ fromX,
1578
+ toX,
1579
+ self: ev.from === ev.to
1580
+ });
1581
+ }
1582
+ if (row.kind === "note" && row.event?.type === "note") {
1583
+ const ev = row.event;
1584
+ const top = orderY.get(ev.order) ?? 0;
1585
+ const h = (orderH.get(ev.order) ?? 36) - ROW_GAP;
1586
+ const spanIds = ev.placement === "over" ? expandParticipantRange(document, ev.participants) : ev.participants;
1587
+ const { x, width } = spanX(participantX, spanIds);
1588
+ const est = estimateLabelBlock(ev.text, Math.max(80, width - NOTE_PAD_X * 2));
1589
+ notes.push({
1590
+ event: ev,
1591
+ x,
1592
+ y: top,
1593
+ width: Math.max(width, est.width + NOTE_PAD_X * 2),
1594
+ height: h
1595
+ });
1596
+ }
1597
+ }
1598
+ const fragments = layoutFragments(document, rows, orderY, orderH, participantX);
1599
+ return { metrics, messages, notes, fragments };
1600
+ }
1601
+ function expandParticipantRange(doc, ids) {
1602
+ if (ids.length <= 1) return ids;
1603
+ const a = participantIndex(doc, ids[0]);
1604
+ const b = participantIndex(doc, ids[ids.length - 1]);
1605
+ const lo = Math.min(a, b);
1606
+ const hi = Math.max(a, b);
1607
+ return doc.participants.slice(lo, hi + 1).map((p) => p.id);
1608
+ }
1609
+ function layoutFragments(document, rows, orderY, orderH, participantX, _startX) {
1610
+ const fragments = [];
1611
+ const stack = [];
1612
+ const completed = [];
1613
+ for (const row of rows) {
1614
+ if (row.kind === "fragmentStart" && row.event?.type === "fragmentStart") {
1615
+ stack.push({ start: row.event, elses: [] });
1616
+ } else if (row.kind === "fragmentElse" && row.event?.type === "fragmentElse") {
1617
+ const top = stack[stack.length - 1];
1618
+ if (top) top.elses.push(row.event);
1619
+ } else if (row.kind === "fragmentEnd" && row.event?.type === "fragmentEnd") {
1620
+ const frame = stack.pop();
1621
+ if (frame) {
1622
+ frame.endOrder = row.event.order;
1623
+ completed.push(frame);
1624
+ }
1625
+ }
1626
+ }
1627
+ const allIds = document.participants.map((p) => p.id);
1628
+ const { x, width } = spanX(participantX, allIds, HEADER_BOX_W / 2 + 8);
1629
+ for (const frame of completed) {
1630
+ const startY = orderY.get(frame.start.order) ?? 0;
1631
+ const endOrder = frame.endOrder ?? frame.start.order;
1632
+ const endY = (orderY.get(endOrder) ?? startY) + (orderH.get(endOrder) ?? 0);
1633
+ const height = Math.max(40, endY - startY);
1634
+ const lanes = [];
1635
+ const firstElse = frame.elses[0];
1636
+ const firstEnd = firstElse ? firstElse.order : endOrder;
1637
+ lanes.push({
1638
+ label: frame.start.label,
1639
+ y: startY,
1640
+ height: Math.max(
1641
+ FRAGMENT_LABEL_H,
1642
+ (orderY.get(firstEnd) ?? startY) - startY
1643
+ ),
1644
+ startOrder: frame.start.order,
1645
+ endOrder: firstEnd
1646
+ });
1647
+ for (let i = 0; i < frame.elses.length; i++) {
1648
+ const el = frame.elses[i];
1649
+ const next = frame.elses[i + 1];
1650
+ const laneEnd = next ? next.order : endOrder;
1651
+ const ly = orderY.get(el.order) ?? startY;
1652
+ lanes.push({
1653
+ label: el.label,
1654
+ y: ly,
1655
+ height: Math.max(
1656
+ FRAGMENT_LABEL_H,
1657
+ (orderY.get(laneEnd) ?? ly) - ly
1658
+ ),
1659
+ startOrder: el.order,
1660
+ endOrder: laneEnd
1661
+ });
1662
+ }
1663
+ fragments.push({
1664
+ id: frame.start.id,
1665
+ kind: frame.start.kind,
1666
+ label: frame.start.label,
1667
+ x: x - 4,
1668
+ y: startY,
1669
+ width: width + 8,
1670
+ height,
1671
+ depth: frame.start.depth,
1672
+ lanes
1673
+ });
1674
+ }
1675
+ return fragments;
1676
+ }
1677
+
1678
+ // src/mermaid/sequence/theme.ts
1679
+ function withAlpha(hex, alpha) {
1680
+ const value = hex.replace("#", "");
1681
+ if (!/^[0-9a-f]{6}$/i.test(value)) return hex;
1682
+ const red = Number.parseInt(value.slice(0, 2), 16);
1683
+ const green = Number.parseInt(value.slice(2, 4), 16);
1684
+ const blue = Number.parseInt(value.slice(4, 6), 16);
1685
+ return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
1686
+ }
1687
+ function getSequenceThemeTokens(mode, themeId = "default") {
1688
+ const arcTheme = getTheme(themeId)[mode];
1689
+ const violet = arcTheme.palette.violet.stroke;
1690
+ const emerald = arcTheme.palette.emerald.stroke;
1691
+ const amber = arcTheme.palette.amber.stroke;
1692
+ if (mode === "dark") {
1693
+ return {
1694
+ background: "#09090b",
1695
+ surface: "#18181b",
1696
+ border: "#3f3f46",
1697
+ text: "#fafafa",
1698
+ textSecondary: "#a1a1aa",
1699
+ textMuted: "#71717a",
1700
+ lifeline: "#3f3f46",
1701
+ messageSolid: "#a1a1aa",
1702
+ messageDashed: "#71717a",
1703
+ messageLabel: "#e4e4e7",
1704
+ actorFill: withAlpha(violet, 0.14),
1705
+ actorStroke: violet,
1706
+ actorText: "#ddd6fe",
1707
+ participantFill: "#18181b",
1708
+ participantStroke: "#52525b",
1709
+ participantText: "#e4e4e7",
1710
+ noteFill: "rgba(39, 39, 42, 0.95)",
1711
+ noteStroke: "#52525b",
1712
+ noteText: "#d4d4d8",
1713
+ localFill: withAlpha(emerald, 0.12),
1714
+ localStroke: emerald,
1715
+ localText: "#a7f3d0",
1716
+ modelFill: withAlpha(amber, 0.12),
1717
+ modelStroke: amber,
1718
+ modelText: "#fde68a",
1719
+ fragmentFill: "rgba(113, 113, 122, 0.08)",
1720
+ fragmentStroke: "#52525b",
1721
+ fragmentLabelBg: "#27272a",
1722
+ fragmentLabelText: "#e4e4e7",
1723
+ fragmentDivider: "#3f3f46",
1724
+ highlight: violet,
1725
+ dimOpacity: 0.45
1726
+ };
1727
+ }
1728
+ return {
1729
+ background: "#ffffff",
1730
+ surface: "#fafafa",
1731
+ border: "#e4e4e7",
1732
+ text: "#18181b",
1733
+ textSecondary: "#52525b",
1734
+ textMuted: "#71717a",
1735
+ lifeline: "#d4d4d8",
1736
+ messageSolid: "#3f3f46",
1737
+ messageDashed: "#71717a",
1738
+ messageLabel: "#27272a",
1739
+ actorFill: withAlpha(violet, 0.1),
1740
+ actorStroke: violet,
1741
+ actorText: "#5b21b6",
1742
+ participantFill: "#ffffff",
1743
+ participantStroke: "#a1a1aa",
1744
+ participantText: "#18181b",
1745
+ noteFill: "#f4f4f5",
1746
+ noteStroke: "#d4d4d8",
1747
+ noteText: "#3f3f46",
1748
+ localFill: withAlpha(emerald, 0.1),
1749
+ localStroke: emerald,
1750
+ localText: "#065f46",
1751
+ modelFill: withAlpha(amber, 0.1),
1752
+ modelStroke: amber,
1753
+ modelText: "#92400e",
1754
+ fragmentFill: "rgba(113, 113, 122, 0.06)",
1755
+ fragmentStroke: "#a1a1aa",
1756
+ fragmentLabelBg: "#f4f4f5",
1757
+ fragmentLabelText: "#3f3f46",
1758
+ fragmentDivider: "#d4d4d8",
1759
+ highlight: violet,
1760
+ dimOpacity: 0.45
1761
+ };
1762
+ }
1763
+ function classifyNoteAccent(text) {
1764
+ const joined = text.join(" ").toLowerCase();
1765
+ if (/no model|local|control plane|sse|broker|menu/.test(joined)) {
1766
+ return "local";
1767
+ }
1768
+ if (/model|inference|token|allowance|api|prompt|skill|agents\.md|reply context/.test(
1769
+ joined
1770
+ )) {
1771
+ return "model";
1772
+ }
1773
+ return "default";
1774
+ }
1775
+ function wrapLines(text, maxChars) {
1776
+ const out = [];
1777
+ for (const segment of text) {
1778
+ if (segment.length <= maxChars) {
1779
+ out.push(segment);
1780
+ continue;
1781
+ }
1782
+ const words = segment.split(/\s+/);
1783
+ let cur = "";
1784
+ for (const w of words) {
1785
+ const next = cur ? `${cur} ${w}` : w;
1786
+ if (next.length > maxChars && cur) {
1787
+ out.push(cur);
1788
+ cur = w;
1789
+ } else {
1790
+ cur = next;
1791
+ }
1792
+ }
1793
+ if (cur) out.push(cur);
1794
+ }
1795
+ return out.length ? out : [""];
1796
+ }
1797
+ function DefaultActorHeader({
1798
+ participant,
1799
+ x,
1800
+ y,
1801
+ width,
1802
+ height,
1803
+ context,
1804
+ emphasized,
1805
+ onPointerEnter,
1806
+ onPointerLeave,
1807
+ onFocus,
1808
+ onBlur,
1809
+ onClick
1810
+ }) {
1811
+ const { tokens, interactive } = context;
1812
+ const opacity = context.dimmed && !emphasized ? tokens.dimOpacity : 1;
1813
+ const cx = x;
1814
+ const top = y + 3;
1815
+ const stroke = emphasized ? tokens.highlight : tokens.actorStroke;
1816
+ return /* @__PURE__ */ jsxs(
1817
+ "g",
1818
+ {
1819
+ opacity,
1820
+ style: { transition: "opacity 160ms ease-out" },
1821
+ onPointerEnter,
1822
+ onPointerLeave,
1823
+ onFocus,
1824
+ onBlur,
1825
+ onClick,
1826
+ tabIndex: interactive ? 0 : void 0,
1827
+ role: interactive ? "button" : void 0,
1828
+ "aria-label": `Actor ${participant.label}`,
1829
+ children: [
1830
+ /* @__PURE__ */ jsx(
1831
+ "circle",
1832
+ {
1833
+ cx,
1834
+ cy: top + 6,
1835
+ r: 6.5,
1836
+ fill: "none",
1837
+ stroke,
1838
+ strokeWidth: 1,
1839
+ opacity: emphasized ? 0.5 : 0.26
1840
+ }
1841
+ ),
1842
+ /* @__PURE__ */ jsx(
1843
+ "circle",
1844
+ {
1845
+ cx,
1846
+ cy: top + 6,
1847
+ r: 3.7,
1848
+ fill: tokens.actorFill,
1849
+ stroke,
1850
+ strokeWidth: emphasized ? 1.7 : 1.3
1851
+ }
1852
+ ),
1853
+ /* @__PURE__ */ jsx(
1854
+ "path",
1855
+ {
1856
+ d: `M ${cx - 9.5} ${top + 22} C ${cx - 9} ${top + 16.5}, ${cx - 5.5} ${top + 14}, ${cx - 2.5} ${top + 13.5} M ${cx + 2.5} ${top + 13.5} C ${cx + 5.5} ${top + 14}, ${cx + 9} ${top + 16.5}, ${cx + 9.5} ${top + 22}`,
1857
+ fill: "none",
1858
+ stroke,
1859
+ strokeWidth: emphasized ? 1.8 : 1.4,
1860
+ strokeLinecap: "round"
1861
+ }
1862
+ ),
1863
+ /* @__PURE__ */ jsx(
1864
+ "text",
1865
+ {
1866
+ x: cx,
1867
+ y: y + height,
1868
+ textAnchor: "middle",
1869
+ fill: tokens.actorText,
1870
+ fontSize: 10,
1871
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
1872
+ fontWeight: 600,
1873
+ children: participant.label
1874
+ }
1875
+ ),
1876
+ /* @__PURE__ */ jsx(
1877
+ "rect",
1878
+ {
1879
+ x: x - width / 2,
1880
+ y,
1881
+ width,
1882
+ height: height + 14,
1883
+ fill: "transparent"
1884
+ }
1885
+ )
1886
+ ]
1887
+ }
1888
+ );
1889
+ }
1890
+ function DefaultParticipantHeader({
1891
+ participant,
1892
+ x,
1893
+ y,
1894
+ width,
1895
+ height,
1896
+ context,
1897
+ emphasized,
1898
+ onPointerEnter,
1899
+ onPointerLeave,
1900
+ onFocus,
1901
+ onBlur,
1902
+ onClick
1903
+ }) {
1904
+ const { tokens, interactive } = context;
1905
+ const opacity = context.dimmed && !emphasized ? tokens.dimOpacity : 1;
1906
+ const boxW = width;
1907
+ const boxH = height;
1908
+ const lines = wrapLines([participant.label], Math.floor(boxW / 7));
1909
+ return /* @__PURE__ */ jsxs(
1910
+ "g",
1911
+ {
1912
+ opacity,
1913
+ style: { transition: "opacity 160ms ease-out" },
1914
+ onPointerEnter,
1915
+ onPointerLeave,
1916
+ onFocus,
1917
+ onBlur,
1918
+ onClick,
1919
+ tabIndex: interactive ? 0 : void 0,
1920
+ role: interactive ? "button" : void 0,
1921
+ "aria-label": `Participant ${participant.label}`,
1922
+ children: [
1923
+ /* @__PURE__ */ jsx(
1924
+ "rect",
1925
+ {
1926
+ x: x - boxW / 2,
1927
+ y,
1928
+ width: boxW,
1929
+ height: boxH,
1930
+ rx: 7,
1931
+ ry: 7,
1932
+ fill: tokens.participantFill,
1933
+ stroke: emphasized ? tokens.highlight : tokens.participantStroke,
1934
+ strokeWidth: emphasized ? 1.75 : 1
1935
+ }
1936
+ ),
1937
+ lines.map((ln, i) => /* @__PURE__ */ jsx(
1938
+ "text",
1939
+ {
1940
+ x,
1941
+ y: y + boxH / 2 + (i - (lines.length - 1) / 2) * 12,
1942
+ textAnchor: "middle",
1943
+ dominantBaseline: "middle",
1944
+ fill: tokens.participantText,
1945
+ fontSize: 10,
1946
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
1947
+ fontWeight: 600,
1948
+ children: ln
1949
+ },
1950
+ i
1951
+ ))
1952
+ ]
1953
+ }
1954
+ );
1955
+ }
1956
+ function DefaultLifeline({
1957
+ participant,
1958
+ x,
1959
+ y1,
1960
+ y2,
1961
+ context,
1962
+ emphasized
1963
+ }) {
1964
+ const { tokens } = context;
1965
+ const opacity = context.dimmed && !emphasized ? tokens.dimOpacity : 1;
1966
+ return /* @__PURE__ */ jsx(
1967
+ "line",
1968
+ {
1969
+ x1: x,
1970
+ y1,
1971
+ x2: x,
1972
+ y2,
1973
+ stroke: emphasized ? tokens.highlight : tokens.lifeline,
1974
+ strokeWidth: emphasized ? 1.5 : 0.85,
1975
+ strokeDasharray: emphasized ? void 0 : "3 4",
1976
+ opacity,
1977
+ style: { transition: "opacity 160ms ease-out, stroke 160ms ease-out" },
1978
+ "data-participant": participant.id
1979
+ }
1980
+ );
1981
+ }
1982
+ function DefaultMessage({
1983
+ laidOut,
1984
+ context,
1985
+ emphasized,
1986
+ onPointerEnter,
1987
+ onPointerLeave,
1988
+ onActivate
1989
+ }) {
1990
+ const { tokens, interactive } = context;
1991
+ const { event, y, fromX, toX, self } = laidOut;
1992
+ const dashed = event.line === "dashed";
1993
+ const stroke = dashed ? tokens.messageDashed : tokens.messageSolid;
1994
+ const color = emphasized ? tokens.highlight : stroke;
1995
+ const opacity = context.dimmed && !emphasized ? tokens.dimOpacity : 1;
1996
+ const markerId = `${context.idPrefix}-arrow-${event.id}`;
1997
+ const label = event.label.join(" ");
1998
+ const lines = wrapLines(event.label, 28);
1999
+ const arrowHead = /* @__PURE__ */ jsx(
2000
+ "marker",
2001
+ {
2002
+ id: markerId,
2003
+ markerWidth: "7",
2004
+ markerHeight: "7",
2005
+ refX: "5.5",
2006
+ refY: "2.75",
2007
+ orient: "auto",
2008
+ markerUnits: "strokeWidth",
2009
+ children: event.arrow === "open" ? /* @__PURE__ */ jsx("path", { d: "M0,0 L5.5,2.75 L0,5.5", fill: "none", stroke: color, strokeWidth: "1.1" }) : /* @__PURE__ */ jsx("path", { d: "M0,0 L5.5,2.75 L0,5.5 Z", fill: color })
2010
+ }
2011
+ );
2012
+ if (self) {
2013
+ const loopW = 36;
2014
+ const loopH = 18;
2015
+ const path = `M ${fromX} ${y - loopH / 2} L ${fromX + loopW} ${y - loopH / 2} L ${fromX + loopW} ${y + loopH / 2} L ${fromX} ${y + loopH / 2}`;
2016
+ return /* @__PURE__ */ jsxs(
2017
+ "g",
2018
+ {
2019
+ opacity,
2020
+ onPointerEnter,
2021
+ onPointerLeave,
2022
+ onClick: onActivate,
2023
+ style: { cursor: interactive ? "pointer" : void 0 },
2024
+ role: interactive ? "button" : void 0,
2025
+ tabIndex: interactive ? 0 : void 0,
2026
+ "aria-label": `Message: ${label}`,
2027
+ onKeyDown: interactive ? (e) => {
2028
+ if (e.key === "Enter" || e.key === " ") {
2029
+ e.preventDefault();
2030
+ onActivate?.();
2031
+ }
2032
+ } : void 0,
2033
+ children: [
2034
+ /* @__PURE__ */ jsx("defs", { children: arrowHead }),
2035
+ /* @__PURE__ */ jsx(
2036
+ "path",
2037
+ {
2038
+ d: path,
2039
+ fill: "none",
2040
+ stroke: color,
2041
+ strokeWidth: emphasized ? 2 : 1.5,
2042
+ strokeDasharray: dashed ? "5 4" : void 0,
2043
+ markerEnd: `url(#${markerId})`
2044
+ }
2045
+ ),
2046
+ /* @__PURE__ */ jsx(
2047
+ "text",
2048
+ {
2049
+ x: fromX + loopW + 6,
2050
+ y: y + 1,
2051
+ fill: tokens.messageLabel,
2052
+ fontSize: 10,
2053
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
2054
+ dominantBaseline: "middle",
2055
+ children: label
2056
+ }
2057
+ )
2058
+ ]
2059
+ }
2060
+ );
2061
+ }
2062
+ const dir = toX >= fromX ? 1 : -1;
2063
+ const x1 = fromX;
2064
+ const x2 = toX - dir * 2;
2065
+ const midX = (x1 + x2) / 2;
2066
+ const labelY = y - 5 - (lines.length - 1) * 5.5;
2067
+ return /* @__PURE__ */ jsxs(
2068
+ "g",
2069
+ {
2070
+ opacity,
2071
+ onPointerEnter,
2072
+ onPointerLeave,
2073
+ onClick: onActivate,
2074
+ style: {
2075
+ cursor: interactive ? "pointer" : void 0,
2076
+ transition: "opacity 160ms ease-out"
2077
+ },
2078
+ role: interactive ? "button" : void 0,
2079
+ tabIndex: interactive ? 0 : void 0,
2080
+ "aria-label": `Message: ${label}`,
2081
+ onKeyDown: interactive ? (e) => {
2082
+ if (e.key === "Enter" || e.key === " ") {
2083
+ e.preventDefault();
2084
+ onActivate?.();
2085
+ }
2086
+ } : void 0,
2087
+ children: [
2088
+ /* @__PURE__ */ jsx("defs", { children: arrowHead }),
2089
+ /* @__PURE__ */ jsx(
2090
+ "line",
2091
+ {
2092
+ x1,
2093
+ y1: y,
2094
+ x2,
2095
+ y2: y,
2096
+ stroke: color,
2097
+ strokeWidth: emphasized ? 2 : 1.35,
2098
+ strokeDasharray: dashed ? "5 4" : void 0,
2099
+ markerEnd: `url(#${markerId})`
2100
+ }
2101
+ ),
2102
+ /* @__PURE__ */ jsx(
2103
+ "line",
2104
+ {
2105
+ x1,
2106
+ y1: y,
2107
+ x2,
2108
+ y2: y,
2109
+ stroke: "transparent",
2110
+ strokeWidth: 12
2111
+ }
2112
+ ),
2113
+ lines.map((ln, i) => /* @__PURE__ */ jsx(
2114
+ "text",
2115
+ {
2116
+ x: midX,
2117
+ y: labelY + i * 11,
2118
+ textAnchor: "middle",
2119
+ fill: tokens.messageLabel,
2120
+ fontSize: 10,
2121
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
2122
+ fontWeight: dashed ? 400 : 500,
2123
+ children: ln
2124
+ },
2125
+ i
2126
+ ))
2127
+ ]
2128
+ }
2129
+ );
2130
+ }
2131
+ function DefaultNote({ laidOut, context, emphasized }) {
2132
+ const { tokens } = context;
2133
+ const accent = context.noteAccent(laidOut.event);
2134
+ const fill = accent === "local" ? tokens.localFill : accent === "model" ? tokens.modelFill : tokens.noteFill;
2135
+ const stroke = accent === "local" ? tokens.localStroke : accent === "model" ? tokens.modelStroke : tokens.noteStroke;
2136
+ const textColor = accent === "local" ? tokens.localText : accent === "model" ? tokens.modelText : tokens.noteText;
2137
+ const opacity = context.dimmed && !emphasized ? tokens.dimOpacity : 1;
2138
+ const lines = wrapLines(
2139
+ laidOut.event.text,
2140
+ Math.max(12, Math.floor((laidOut.width - 16) / 6.5))
2141
+ );
2142
+ const { x, y, width, height } = laidOut;
2143
+ return /* @__PURE__ */ jsxs("g", { opacity, style: { transition: "opacity 160ms ease-out" }, children: [
2144
+ /* @__PURE__ */ jsx(
2145
+ "rect",
2146
+ {
2147
+ x,
2148
+ y,
2149
+ width,
2150
+ height,
2151
+ rx: 8,
2152
+ ry: 8,
2153
+ fill,
2154
+ stroke: emphasized ? tokens.highlight : stroke,
2155
+ strokeWidth: emphasized ? 1.5 : 1
2156
+ }
2157
+ ),
2158
+ lines.map((ln, i) => /* @__PURE__ */ jsx(
2159
+ "text",
2160
+ {
2161
+ x: x + width / 2,
2162
+ y: y + height / 2 + (i - (lines.length - 1) / 2) * 12,
2163
+ textAnchor: "middle",
2164
+ dominantBaseline: "middle",
2165
+ fill: textColor,
2166
+ fontSize: 10,
2167
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
2168
+ children: ln
2169
+ },
2170
+ i
2171
+ ))
2172
+ ] });
2173
+ }
2174
+ function DefaultFragment({ laidOut, context }) {
2175
+ const { tokens } = context;
2176
+ const { x, y, width, height, kind, label, lanes } = laidOut;
2177
+ const kindLabel = kind.toUpperCase();
2178
+ return /* @__PURE__ */ jsxs("g", { children: [
2179
+ /* @__PURE__ */ jsx(
2180
+ "rect",
2181
+ {
2182
+ x,
2183
+ y,
2184
+ width,
2185
+ height,
2186
+ rx: 8,
2187
+ ry: 8,
2188
+ fill: tokens.fragmentFill,
2189
+ stroke: tokens.fragmentStroke,
2190
+ strokeWidth: 1
2191
+ }
2192
+ ),
2193
+ /* @__PURE__ */ jsx(
2194
+ "rect",
2195
+ {
2196
+ x: x + 7,
2197
+ y: y + 4,
2198
+ width: Math.max(34, kindLabel.length * 6.5 + 14),
2199
+ height: 16,
2200
+ rx: 4,
2201
+ ry: 4,
2202
+ fill: tokens.fragmentLabelBg,
2203
+ stroke: tokens.fragmentStroke,
2204
+ strokeWidth: 1
2205
+ }
2206
+ ),
2207
+ /* @__PURE__ */ jsx(
2208
+ "text",
2209
+ {
2210
+ x: x + 7 + Math.max(34, kindLabel.length * 6.5 + 14) / 2,
2211
+ y: y + 12,
2212
+ textAnchor: "middle",
2213
+ dominantBaseline: "middle",
2214
+ fill: tokens.fragmentLabelText,
2215
+ fontSize: 9,
2216
+ fontWeight: 700,
2217
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
2218
+ children: kindLabel
2219
+ }
2220
+ ),
2221
+ label && /* @__PURE__ */ jsx(
2222
+ "text",
2223
+ {
2224
+ x: x + 7 + Math.max(34, kindLabel.length * 6.5 + 14) + 7,
2225
+ y: y + 12,
2226
+ dominantBaseline: "middle",
2227
+ fill: tokens.textSecondary,
2228
+ fontSize: 10,
2229
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
2230
+ children: label
2231
+ }
2232
+ ),
2233
+ lanes.slice(1).map((lane, i) => /* @__PURE__ */ jsxs("g", { children: [
2234
+ /* @__PURE__ */ jsx(
2235
+ "line",
2236
+ {
2237
+ x1: x + 6,
2238
+ y1: lane.y,
2239
+ x2: x + width - 6,
2240
+ y2: lane.y,
2241
+ stroke: tokens.fragmentDivider,
2242
+ strokeWidth: 1,
2243
+ strokeDasharray: "4 3"
2244
+ }
2245
+ ),
2246
+ /* @__PURE__ */ jsx(
2247
+ "text",
2248
+ {
2249
+ x: x + 12,
2250
+ y: lane.y + 12,
2251
+ fill: tokens.textSecondary,
2252
+ fontSize: 10,
2253
+ fontWeight: 600,
2254
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
2255
+ children: `else${lane.label ? ` ${lane.label}` : ""}`
2256
+ }
2257
+ )
2258
+ ] }, i))
2259
+ ] });
2260
+ }
2261
+ var defaultSequenceParts = {
2262
+ ActorHeader: DefaultActorHeader,
2263
+ ParticipantHeader: DefaultParticipantHeader,
2264
+ Lifeline: DefaultLifeline,
2265
+ Message: DefaultMessage,
2266
+ Note: DefaultNote,
2267
+ Fragment: DefaultFragment
2268
+ };
2269
+ function resolveParts(overrides) {
2270
+ return {
2271
+ ActorHeader: overrides?.ActorHeader ?? defaultSequenceParts.ActorHeader,
2272
+ ParticipantHeader: overrides?.ParticipantHeader ?? defaultSequenceParts.ParticipantHeader,
2273
+ Lifeline: overrides?.Lifeline ?? defaultSequenceParts.Lifeline,
2274
+ Message: overrides?.Message ?? defaultSequenceParts.Message,
2275
+ Note: overrides?.Note ?? defaultSequenceParts.Note,
2276
+ Fragment: overrides?.Fragment ?? defaultSequenceParts.Fragment
2277
+ };
2278
+ }
2279
+ function neutralNoteAccent() {
2280
+ return "default";
2281
+ }
2282
+ function SequenceRenderer({
2283
+ document,
2284
+ mode = "light",
2285
+ theme = "default",
2286
+ interactive = true,
2287
+ className = "",
2288
+ title,
2289
+ description,
2290
+ presentation
2291
+ }) {
2292
+ const idPrefix = `arc-seq-${useId().replace(/:/g, "")}`;
2293
+ const tokens = useMemo(
2294
+ () => ({
2295
+ ...getSequenceThemeTokens(mode, theme),
2296
+ ...presentation?.tokens
2297
+ }),
2298
+ [mode, theme, presentation?.tokens]
2299
+ );
2300
+ const layout = useMemo(
2301
+ () => layoutSequence(document, {
2302
+ width: presentation?.width ?? 960,
2303
+ maxLabelWidth: presentation?.maxLabelWidth ?? 160
2304
+ }),
2305
+ [document, presentation?.width, presentation?.maxLabelWidth]
2306
+ );
2307
+ const parts = useMemo(
2308
+ () => resolveParts(presentation?.parts),
2309
+ [presentation?.parts]
2310
+ );
2311
+ const interactions = presentation?.interactions;
2312
+ const activeMessage = useMemo(
2313
+ () => interactions?.activeMessageId ? document.events.find(
2314
+ (event) => event.type === "message" && event.id === interactions.activeMessageId
2315
+ ) : void 0,
2316
+ [document.events, interactions?.activeMessageId]
2317
+ );
2318
+ const controlledHighlight = interactions?.highlightedParticipantId;
2319
+ const [internalHighlight, setInternalHighlight] = useState(null);
2320
+ const highlight = controlledHighlight !== void 0 ? controlledHighlight : internalHighlight;
2321
+ const dimUnrelated = interactions?.dimUnrelated ?? (interactive && (highlight != null || activeMessage != null));
2322
+ const relatedIds = useMemo(() => {
2323
+ if (!highlight) return null;
2324
+ const set = /* @__PURE__ */ new Set([highlight]);
2325
+ for (const m of document.events) {
2326
+ if (m.type === "message" && (m.from === highlight || m.to === highlight)) {
2327
+ set.add(m.from);
2328
+ set.add(m.to);
2329
+ }
2330
+ if (m.type === "note" && m.participants.includes(highlight)) {
2331
+ m.participants.forEach((p) => set.add(p));
2332
+ }
2333
+ }
2334
+ return set;
2335
+ }, [document.events, highlight]);
2336
+ const isEmphasizedParticipant = useCallback(
2337
+ (id) => {
2338
+ if (activeMessage) {
2339
+ return activeMessage.from === id || activeMessage.to === id;
2340
+ }
2341
+ if (!highlight) return false;
2342
+ return relatedIds?.has(id) ?? id === highlight;
2343
+ },
2344
+ [activeMessage, highlight, relatedIds]
2345
+ );
2346
+ const isEmphasizedMessage = useCallback(
2347
+ (id, from, to) => {
2348
+ if (activeMessage) {
2349
+ return activeMessage.id === id;
2350
+ }
2351
+ if (!highlight) return false;
2352
+ return from === highlight || to === highlight;
2353
+ },
2354
+ [activeMessage, highlight]
2355
+ );
2356
+ const setHighlight = useCallback(
2357
+ (id) => {
2358
+ if (controlledHighlight === void 0) setInternalHighlight(id);
2359
+ interactions?.onParticipantHover?.(id);
2360
+ },
2361
+ [controlledHighlight, interactions]
2362
+ );
2363
+ const a11yTitle = title ?? `Sequence diagram with ${document.participants.length} participants`;
2364
+ const a11yDesc = description ?? buildAutoDescription(document);
2365
+ const context = {
2366
+ idPrefix,
2367
+ mode,
2368
+ themeId: theme,
2369
+ tokens,
2370
+ layout,
2371
+ document,
2372
+ interactive,
2373
+ highlightedParticipantId: highlight,
2374
+ dimmed: Boolean(dimUnrelated && (highlight || activeMessage)),
2375
+ noteAccent: presentation?.noteAccent ?? neutralNoteAccent
2376
+ };
2377
+ const { metrics } = layout;
2378
+ const lifelineTop = PADDING_Y + HEADER_HEIGHT;
2379
+ const lifelineBottom = metrics.height - 12;
2380
+ const {
2381
+ ActorHeader,
2382
+ ParticipantHeader,
2383
+ Lifeline,
2384
+ Message,
2385
+ Note,
2386
+ Fragment: Fragment2
2387
+ } = parts;
2388
+ return /* @__PURE__ */ jsx(
2389
+ "div",
2390
+ {
2391
+ className,
2392
+ style: {
2393
+ width: "100%",
2394
+ minWidth: 0,
2395
+ overflowX: "auto",
2396
+ ...presentation?.style
2397
+ },
2398
+ "data-arc-mermaid-family": "sequence",
2399
+ children: /* @__PURE__ */ jsxs(
2400
+ "svg",
2401
+ {
2402
+ viewBox: `0 0 ${metrics.width} ${metrics.height}`,
2403
+ width: `${(presentation?.scale ?? 1) * 100}%`,
2404
+ height: "auto",
2405
+ role: "img",
2406
+ "aria-labelledby": `${idPrefix}-title ${idPrefix}-desc`,
2407
+ style: {
2408
+ display: "block",
2409
+ background: tokens.background,
2410
+ borderRadius: 10,
2411
+ minWidth: Math.min(metrics.width * (presentation?.scale ?? 1), 680),
2412
+ maxWidth: metrics.width * (presentation?.scale ?? 1)
2413
+ },
2414
+ children: [
2415
+ /* @__PURE__ */ jsx("title", { id: `${idPrefix}-title`, children: a11yTitle }),
2416
+ /* @__PURE__ */ jsx("desc", { id: `${idPrefix}-desc`, children: a11yDesc }),
2417
+ /* @__PURE__ */ jsx(
2418
+ "rect",
2419
+ {
2420
+ x: 0,
2421
+ y: 0,
2422
+ width: metrics.width,
2423
+ height: metrics.height,
2424
+ fill: tokens.background,
2425
+ rx: 10
2426
+ }
2427
+ ),
2428
+ layout.fragments.map((f) => /* @__PURE__ */ jsx(Fragment2, { laidOut: f, context }, f.id)),
2429
+ document.participants.map((p) => /* @__PURE__ */ jsx(
2430
+ Lifeline,
2431
+ {
2432
+ participant: p,
2433
+ x: metrics.participantX[p.id],
2434
+ y1: lifelineTop,
2435
+ y2: lifelineBottom,
2436
+ context,
2437
+ emphasized: isEmphasizedParticipant(p.id)
2438
+ },
2439
+ `life-${p.id}`
2440
+ )),
2441
+ document.participants.map((p) => {
2442
+ const x = metrics.participantX[p.id];
2443
+ const y = PADDING_Y;
2444
+ const emphasized = isEmphasizedParticipant(p.id);
2445
+ const handlers = interactive ? {
2446
+ onPointerEnter: () => setHighlight(p.id),
2447
+ onPointerLeave: () => setHighlight(null),
2448
+ onFocus: () => {
2449
+ setHighlight(p.id);
2450
+ interactions?.onParticipantFocus?.(p.id);
2451
+ },
2452
+ onBlur: () => {
2453
+ setHighlight(null);
2454
+ interactions?.onParticipantFocus?.(null);
2455
+ },
2456
+ onClick: () => interactions?.onParticipantActivate?.(p.id)
2457
+ } : {};
2458
+ if (p.kind === "actor") {
2459
+ return /* @__PURE__ */ jsx(
2460
+ ActorHeader,
2461
+ {
2462
+ participant: p,
2463
+ x,
2464
+ y,
2465
+ width: HEADER_BOX_W,
2466
+ height: HEADER_BOX_H + 8,
2467
+ context,
2468
+ emphasized,
2469
+ ...handlers
2470
+ },
2471
+ p.id
2472
+ );
2473
+ }
2474
+ return /* @__PURE__ */ jsx(
2475
+ ParticipantHeader,
2476
+ {
2477
+ participant: p,
2478
+ x,
2479
+ y,
2480
+ width: HEADER_BOX_W,
2481
+ height: HEADER_BOX_H,
2482
+ context,
2483
+ emphasized,
2484
+ ...handlers
2485
+ },
2486
+ p.id
2487
+ );
2488
+ }),
2489
+ layout.notes.map((n) => /* @__PURE__ */ jsx(
2490
+ Note,
2491
+ {
2492
+ laidOut: n,
2493
+ context,
2494
+ emphasized: highlight != null && n.event.participants.some((id) => relatedIds?.has(id))
2495
+ },
2496
+ n.event.id
2497
+ )),
2498
+ layout.messages.map((m) => /* @__PURE__ */ jsx(
2499
+ Message,
2500
+ {
2501
+ laidOut: m,
2502
+ context,
2503
+ emphasized: isEmphasizedMessage(
2504
+ m.event.id,
2505
+ m.event.from,
2506
+ m.event.to
2507
+ ),
2508
+ onPointerEnter: interactive ? () => interactions?.onMessageHover?.(m.event.id) : void 0,
2509
+ onPointerLeave: interactive ? () => interactions?.onMessageHover?.(null) : void 0,
2510
+ onActivate: interactive ? () => interactions?.onMessageActivate?.(m.event.id) : void 0
2511
+ },
2512
+ m.event.id
2513
+ ))
2514
+ ]
2515
+ }
2516
+ )
2517
+ }
2518
+ );
2519
+ }
2520
+ function buildAutoDescription(doc) {
2521
+ const names = doc.participants.map((p) => p.label).join(", ");
2522
+ const msgCount = doc.events.filter((e) => e.type === "message").length;
2523
+ const noteCount = doc.events.filter((e) => e.type === "note").length;
2524
+ const fragCount = doc.events.filter((e) => e.type === "fragmentStart").length;
2525
+ return `Participants: ${names}. ${msgCount} messages, ${noteCount} notes, ${fragCount} fragments.`;
2526
+ }
2527
+ function SequenceFamilyRenderer({
2528
+ document,
2529
+ mode,
2530
+ theme,
2531
+ interactive,
2532
+ className,
2533
+ title,
2534
+ description,
2535
+ sequence
2536
+ }) {
2537
+ if (document.family !== "sequence") return null;
2538
+ return /* @__PURE__ */ jsx(
2539
+ SequenceRenderer,
2540
+ {
2541
+ document,
2542
+ mode,
2543
+ theme,
2544
+ interactive,
2545
+ className,
2546
+ title,
2547
+ description,
2548
+ presentation: sequence
2549
+ }
2550
+ );
2551
+ }
2552
+ function PendingFamilyRenderer({ document, className }) {
2553
+ return /* @__PURE__ */ jsxs(
2554
+ "div",
2555
+ {
2556
+ className,
2557
+ "data-arc-mermaid-family": document.family,
2558
+ style: {
2559
+ padding: 16,
2560
+ borderRadius: 12,
2561
+ border: "1px dashed #a1a1aa",
2562
+ color: "#52525b",
2563
+ fontFamily: "ui-sans-serif, system-ui, sans-serif",
2564
+ fontSize: 13
2565
+ },
2566
+ children: [
2567
+ "Native ",
2568
+ /* @__PURE__ */ jsx("strong", { children: document.family }),
2569
+ " rendering is not available yet."
2570
+ ]
2571
+ }
2572
+ );
2573
+ }
2574
+ var registry = /* @__PURE__ */ new Map([
2575
+ ["sequence", SequenceFamilyRenderer],
2576
+ ["flowchart", PendingFamilyRenderer],
2577
+ ["state", PendingFamilyRenderer]
2578
+ ]);
2579
+ function registerMermaidRenderer(family, renderer) {
2580
+ registry.set(family, renderer);
2581
+ }
2582
+ function getMermaidRenderer(family) {
2583
+ return registry.get(family);
2584
+ }
2585
+ function listMermaidRenderers() {
2586
+ return [...registry.keys()];
2587
+ }
2588
+ function ArcMermaid({
2589
+ source,
2590
+ document: documentProp,
2591
+ mode = "light",
2592
+ theme = "default",
2593
+ interactive = true,
2594
+ className = "",
2595
+ title,
2596
+ description,
2597
+ sequence
2598
+ }) {
2599
+ const parsed = useMemo(() => {
2600
+ if (documentProp) {
2601
+ return {
2602
+ document: documentProp,
2603
+ diagnostics: [],
2604
+ source: source ?? ""
2605
+ };
2606
+ }
2607
+ if (source != null) return parseMermaid(source);
2608
+ return {
2609
+ document: null,
2610
+ diagnostics: [
2611
+ {
2612
+ severity: "error",
2613
+ code: "no-input",
2614
+ message: "ArcMermaid requires source or document"
2615
+ }
2616
+ ],
2617
+ source: ""
2618
+ };
2619
+ }, [source, documentProp]);
2620
+ const { document, diagnostics } = parsed;
2621
+ if (!document) {
2622
+ return /* @__PURE__ */ jsx(
2623
+ "div",
2624
+ {
2625
+ className,
2626
+ role: "alert",
2627
+ style: {
2628
+ padding: 16,
2629
+ borderRadius: 12,
2630
+ border: "1px solid #fca5a5",
2631
+ background: "#fef2f2",
2632
+ color: "#991b1b",
2633
+ fontFamily: "ui-sans-serif, system-ui, sans-serif",
2634
+ fontSize: 13
2635
+ },
2636
+ children: diagnostics.map((d, i) => /* @__PURE__ */ jsx("div", { children: d.message }, i))
2637
+ }
2638
+ );
2639
+ }
2640
+ const Renderer = getMermaidRenderer(document.family);
2641
+ if (!Renderer) {
2642
+ return /* @__PURE__ */ jsxs("div", { className, role: "alert", children: [
2643
+ "No renderer registered for family \u201C",
2644
+ document.family,
2645
+ "\u201D."
2646
+ ] });
2647
+ }
2648
+ return /* @__PURE__ */ jsx(
2649
+ Renderer,
2650
+ {
2651
+ document,
2652
+ mode,
2653
+ theme,
2654
+ interactive,
2655
+ className,
2656
+ title,
2657
+ description,
2658
+ sequence
2659
+ }
2660
+ );
2661
+ }
2662
+ function getSequenceDocument(source, document) {
2663
+ if (document?.family === "sequence") return document;
2664
+ if (document) return null;
2665
+ const parsed = source == null ? null : parseMermaid(source).document;
2666
+ return parsed?.family === "sequence" ? parsed : null;
2667
+ }
2668
+ function ArcMermaidPlayer({
2669
+ source,
2670
+ document,
2671
+ mode = "light",
2672
+ theme = "default",
2673
+ className = "",
2674
+ title,
2675
+ description,
2676
+ sequence,
2677
+ defaultZoom = "fit",
2678
+ minZoom = 0.5,
2679
+ maxZoom = 2.5,
2680
+ zoomStep = 0.2,
2681
+ showControls = true,
2682
+ showPlayback = true,
2683
+ autoplayMs = 1400,
2684
+ onStepChange
2685
+ }) {
2686
+ const rootRef = useRef(null);
2687
+ const sequenceDocument = useMemo(
2688
+ () => getSequenceDocument(source, document),
2689
+ [source, document]
2690
+ );
2691
+ const steps = useMemo(
2692
+ () => sequenceDocument?.events.filter(
2693
+ (event) => event.type === "message"
2694
+ ) ?? [],
2695
+ [sequenceDocument]
2696
+ );
2697
+ const [zoom, setZoom] = useState(defaultZoom === "fit" ? 1 : defaultZoom);
2698
+ const [stepIndex, setStepIndex] = useState(-1);
2699
+ const [playing, setPlaying] = useState(false);
2700
+ const [fullscreen, setFullscreen] = useState(false);
2701
+ const activeMessageId = stepIndex >= 0 && stepIndex < steps.length ? steps[stepIndex].id : null;
2702
+ useEffect(() => {
2703
+ onStepChange?.(stepIndex, activeMessageId);
2704
+ }, [activeMessageId, onStepChange, stepIndex]);
2705
+ useEffect(() => {
2706
+ if (!playing || steps.length === 0) return;
2707
+ const timer = window.setInterval(() => {
2708
+ setStepIndex((current) => {
2709
+ if (current >= steps.length - 1) {
2710
+ setPlaying(false);
2711
+ return current;
2712
+ }
2713
+ return current + 1;
2714
+ });
2715
+ }, autoplayMs);
2716
+ return () => window.clearInterval(timer);
2717
+ }, [autoplayMs, playing, steps.length]);
2718
+ useEffect(() => {
2719
+ const syncFullscreen = () => {
2720
+ setFullscreen(documentElementFullscreen() === rootRef.current);
2721
+ };
2722
+ globalThis.document?.addEventListener("fullscreenchange", syncFullscreen);
2723
+ return () => globalThis.document?.removeEventListener("fullscreenchange", syncFullscreen);
2724
+ }, []);
2725
+ const setBoundedZoom = (next) => {
2726
+ setZoom(Math.min(maxZoom, Math.max(minZoom, Number(next.toFixed(2)))));
2727
+ };
2728
+ const moveStep = (delta) => {
2729
+ setPlaying(false);
2730
+ setStepIndex(
2731
+ (current) => Math.min(steps.length - 1, Math.max(-1, current + delta))
2732
+ );
2733
+ };
2734
+ const togglePlayback = () => {
2735
+ if (steps.length === 0) return;
2736
+ if (stepIndex >= steps.length - 1) setStepIndex(-1);
2737
+ setPlaying((value) => !value);
2738
+ };
2739
+ const toggleFullscreen = async () => {
2740
+ const element = rootRef.current;
2741
+ if (!element) return;
2742
+ if (documentElementFullscreen() === element) {
2743
+ await globalThis.document.exitFullscreen?.();
2744
+ } else {
2745
+ await element.requestFullscreen?.();
2746
+ }
2747
+ };
2748
+ const mergedSequence = {
2749
+ ...sequence,
2750
+ scale: zoom * (sequence?.scale ?? 1),
2751
+ interactions: {
2752
+ ...sequence?.interactions,
2753
+ activeMessageId: sequence?.interactions?.activeMessageId !== void 0 ? sequence.interactions.activeMessageId : activeMessageId
2754
+ }
2755
+ };
2756
+ const dark = mode === "dark";
2757
+ const playerTokens = useMemo(
2758
+ () => getSequenceThemeTokens(mode, theme),
2759
+ [mode, theme]
2760
+ );
2761
+ const chrome = {
2762
+ background: playerTokens.background,
2763
+ border: playerTokens.border,
2764
+ surface: dark ? "rgba(24,24,27,.9)" : "rgba(255,255,255,.92)",
2765
+ text: playerTokens.text,
2766
+ muted: playerTokens.textSecondary,
2767
+ active: playerTokens.highlight
2768
+ };
2769
+ return /* @__PURE__ */ jsxs(
2770
+ "div",
2771
+ {
2772
+ ref: rootRef,
2773
+ className,
2774
+ tabIndex: 0,
2775
+ onKeyDown: (event) => {
2776
+ if (event.key === "ArrowRight" && showPlayback) moveStep(1);
2777
+ if (event.key === "ArrowLeft" && showPlayback) moveStep(-1);
2778
+ },
2779
+ style: {
2780
+ display: "flex",
2781
+ flexDirection: "column",
2782
+ width: "100%",
2783
+ minWidth: 0,
2784
+ minHeight: fullscreen ? "100vh" : void 0,
2785
+ background: chrome.background,
2786
+ border: `1px solid ${chrome.border}`,
2787
+ borderRadius: fullscreen ? 0 : 12,
2788
+ overflow: "hidden",
2789
+ color: chrome.text,
2790
+ outline: "none"
2791
+ },
2792
+ "data-arc-mermaid-player": true,
2793
+ children: [
2794
+ showControls && /* @__PURE__ */ jsxs(
2795
+ "div",
2796
+ {
2797
+ style: {
2798
+ minHeight: 44,
2799
+ display: "flex",
2800
+ flexWrap: "wrap",
2801
+ alignItems: "center",
2802
+ gap: 6,
2803
+ padding: "6px 8px",
2804
+ borderBottom: `1px solid ${chrome.border}`,
2805
+ background: chrome.surface,
2806
+ backdropFilter: "blur(14px)",
2807
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif"
2808
+ },
2809
+ children: [
2810
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
2811
+ /* @__PURE__ */ jsx(
2812
+ ControlButton,
2813
+ {
2814
+ label: "Zoom out",
2815
+ color: chrome.text,
2816
+ border: chrome.border,
2817
+ onClick: () => setBoundedZoom(zoom - zoomStep),
2818
+ children: /* @__PURE__ */ jsx(Minus, { size: 15 })
2819
+ }
2820
+ ),
2821
+ /* @__PURE__ */ jsxs(
2822
+ "button",
2823
+ {
2824
+ type: "button",
2825
+ onClick: () => setZoom(1),
2826
+ title: "Fit diagram",
2827
+ style: {
2828
+ height: 28,
2829
+ minWidth: 50,
2830
+ border: `1px solid ${chrome.border}`,
2831
+ borderRadius: 8,
2832
+ background: "transparent",
2833
+ color: chrome.muted,
2834
+ fontSize: 11,
2835
+ fontWeight: 650,
2836
+ cursor: "pointer"
2837
+ },
2838
+ children: [
2839
+ Math.round(zoom * 100),
2840
+ "%"
2841
+ ]
2842
+ }
2843
+ ),
2844
+ /* @__PURE__ */ jsx(
2845
+ ControlButton,
2846
+ {
2847
+ label: "Zoom in",
2848
+ color: chrome.text,
2849
+ border: chrome.border,
2850
+ onClick: () => setBoundedZoom(zoom + zoomStep),
2851
+ children: /* @__PURE__ */ jsx(Plus, { size: 15 })
2852
+ }
2853
+ ),
2854
+ /* @__PURE__ */ jsx(
2855
+ ControlButton,
2856
+ {
2857
+ label: "Fit diagram",
2858
+ color: chrome.text,
2859
+ border: chrome.border,
2860
+ onClick: () => setZoom(1),
2861
+ children: /* @__PURE__ */ jsx(Scan, { size: 15 })
2862
+ }
2863
+ )
2864
+ ] }),
2865
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
2866
+ /* @__PURE__ */ jsxs(
2867
+ "div",
2868
+ {
2869
+ style: {
2870
+ display: "flex",
2871
+ alignItems: "center",
2872
+ gap: 5,
2873
+ marginLeft: "auto"
2874
+ },
2875
+ children: [
2876
+ showPlayback && steps.length > 0 && /* @__PURE__ */ jsxs(
2877
+ "div",
2878
+ {
2879
+ style: {
2880
+ display: "flex",
2881
+ alignItems: "center",
2882
+ gap: 5
2883
+ },
2884
+ children: [
2885
+ /* @__PURE__ */ jsx(
2886
+ ControlButton,
2887
+ {
2888
+ label: "Previous message",
2889
+ color: chrome.text,
2890
+ border: chrome.border,
2891
+ disabled: stepIndex < 0,
2892
+ onClick: () => moveStep(-1),
2893
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16 })
2894
+ }
2895
+ ),
2896
+ /* @__PURE__ */ jsx(
2897
+ ControlButton,
2898
+ {
2899
+ label: playing ? "Pause sequence" : "Play sequence",
2900
+ color: playing ? chrome.active : chrome.text,
2901
+ border: playing ? chrome.active : chrome.border,
2902
+ onClick: togglePlayback,
2903
+ children: playing ? /* @__PURE__ */ jsx(Pause, { size: 15 }) : /* @__PURE__ */ jsx(Play, { size: 15 })
2904
+ }
2905
+ ),
2906
+ /* @__PURE__ */ jsx(
2907
+ ControlButton,
2908
+ {
2909
+ label: "Next message",
2910
+ color: chrome.text,
2911
+ border: chrome.border,
2912
+ disabled: stepIndex >= steps.length - 1,
2913
+ onClick: () => moveStep(1),
2914
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 16 })
2915
+ }
2916
+ ),
2917
+ /* @__PURE__ */ jsx(
2918
+ "span",
2919
+ {
2920
+ "aria-live": "polite",
2921
+ style: {
2922
+ minWidth: 68,
2923
+ color: chrome.muted,
2924
+ fontSize: 11,
2925
+ textAlign: "center",
2926
+ fontVariantNumeric: "tabular-nums"
2927
+ },
2928
+ children: stepIndex < 0 ? "Overview" : `${stepIndex + 1} / ${steps.length}`
2929
+ }
2930
+ )
2931
+ ]
2932
+ }
2933
+ ),
2934
+ /* @__PURE__ */ jsx(
2935
+ ControlButton,
2936
+ {
2937
+ label: fullscreen ? "Exit fullscreen" : "Expand player",
2938
+ color: chrome.text,
2939
+ border: chrome.border,
2940
+ onClick: toggleFullscreen,
2941
+ children: /* @__PURE__ */ jsx(Expand, { size: 15 })
2942
+ }
2943
+ )
2944
+ ]
2945
+ }
2946
+ )
2947
+ ]
2948
+ }
2949
+ ),
2950
+ /* @__PURE__ */ jsx(
2951
+ "div",
2952
+ {
2953
+ style: {
2954
+ flex: 1,
2955
+ minWidth: 0,
2956
+ overflow: "auto",
2957
+ padding: fullscreen ? 20 : 10,
2958
+ background: chrome.background
2959
+ },
2960
+ children: /* @__PURE__ */ jsx(
2961
+ ArcMermaid,
2962
+ {
2963
+ source,
2964
+ document,
2965
+ mode,
2966
+ theme,
2967
+ interactive: true,
2968
+ title,
2969
+ description,
2970
+ sequence: mergedSequence
2971
+ }
2972
+ )
2973
+ }
2974
+ )
2975
+ ]
2976
+ }
2977
+ );
2978
+ }
2979
+ function documentElementFullscreen() {
2980
+ return typeof globalThis.document === "undefined" ? null : globalThis.document.fullscreenElement;
2981
+ }
2982
+ function ControlButton({
2983
+ label,
2984
+ color,
2985
+ border,
2986
+ disabled,
2987
+ onClick,
2988
+ children
2989
+ }) {
2990
+ return /* @__PURE__ */ jsx(
2991
+ "button",
2992
+ {
2993
+ type: "button",
2994
+ "aria-label": label,
2995
+ title: label,
2996
+ disabled,
2997
+ onClick,
2998
+ style: {
2999
+ width: 28,
3000
+ height: 28,
3001
+ display: "grid",
3002
+ placeItems: "center",
3003
+ border: `1px solid ${border}`,
3004
+ borderRadius: 8,
3005
+ background: "transparent",
3006
+ color,
3007
+ opacity: disabled ? 0.34 : 1,
3008
+ cursor: disabled ? "default" : "pointer"
3009
+ },
3010
+ children
3011
+ }
3012
+ );
3013
+ }
3014
+
3015
+ export { ArcDiagram, ArcMermaid, ArcMermaidPlayer, DEFAULT_THEME, SequenceRenderer, THEMES, autoLayout, classifyNoteAccent, createAutoLayout, ArcDiagram_default as default, defaultSequenceParts, getMermaidRenderer, getSequenceThemeTokens, getTheme, getThemeList, layoutSequence, listMermaidRenderers, parseMermaid, parseSequenceSource, registerMermaidRenderer };
3016
+ //# sourceMappingURL=index.js.map
3017
+ //# sourceMappingURL=index.js.map