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