@x12i/diagrams 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js ADDED
@@ -0,0 +1,2525 @@
1
+ "use client";
2
+ import {
3
+ DiagramMutationError,
4
+ GRID_SIZE,
5
+ NODE_KINDS,
6
+ addNode,
7
+ alignNodes,
8
+ buildIndex,
9
+ checkConnection,
10
+ computeEdgePath,
11
+ connect,
12
+ createDiagram,
13
+ createHistory,
14
+ defaultMessages,
15
+ defaultPalette,
16
+ diagramToOutline,
17
+ diagramToOutlineTree,
18
+ diagramToSvgTree,
19
+ disconnect,
20
+ distributeNodes,
21
+ duplicateNodes,
22
+ exportDiagramJson,
23
+ exportDiagramSvg,
24
+ extractFragment,
25
+ findDirectionalNeighbor,
26
+ formatMessage,
27
+ getAbsoluteBounds,
28
+ getAncestors,
29
+ getNodeSize,
30
+ getPortPlacements,
31
+ getReadingOrder,
32
+ groupNodes,
33
+ historyReducer,
34
+ isContainerKind,
35
+ isSafeImageUrl,
36
+ layoutedDiagram,
37
+ moveNodes,
38
+ nextEdgeId,
39
+ nextNodeId,
40
+ pasteFragment,
41
+ removeEdges,
42
+ removeNodes,
43
+ resizeNode,
44
+ resolveMessages,
45
+ snapPoint,
46
+ summarizeNodes,
47
+ translateNodes,
48
+ ungroupNode,
49
+ updateEdge,
50
+ updateNode
51
+ } from "./chunk-3DJFQRU3.js";
52
+
53
+ // src/react/canvas.tsx
54
+ import { VisuallyHidden } from "@x12i/ui";
55
+ import {
56
+ Background,
57
+ BackgroundVariant,
58
+ MiniMap,
59
+ ReactFlow,
60
+ ReactFlowProvider,
61
+ useReactFlow
62
+ } from "@xyflow/react";
63
+ import {
64
+ useCallback as useCallback2,
65
+ useEffect as useEffect3,
66
+ useId as useId3,
67
+ useImperativeHandle,
68
+ useLayoutEffect,
69
+ useMemo as useMemo3,
70
+ useRef as useRef4,
71
+ useState as useState3
72
+ } from "react";
73
+
74
+ // src/react/context.ts
75
+ import { createContext, useContext } from "react";
76
+ var CanvasContext = createContext(null);
77
+ function useCanvasContext() {
78
+ const value = useContext(CanvasContext);
79
+ if (value === null) throw new Error("@x12i/diagrams: node and edge components must be rendered inside DiagramCanvas.");
80
+ return value;
81
+ }
82
+
83
+ // src/react/edges.tsx
84
+ import { BaseEdge, EdgeLabelRenderer } from "@xyflow/react";
85
+ import { memo, useEffect, useRef, useState } from "react";
86
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
87
+ var TONES = ["neutral", "success", "warning", "danger", "info"];
88
+ function EdgeMarkers({ markerId }) {
89
+ return /* @__PURE__ */ jsx("svg", { "data-kit": "diagram-markers", width: "0", height: "0", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("defs", { children: TONES.map((tone) => /* @__PURE__ */ jsx(
90
+ "marker",
91
+ {
92
+ id: markerId(tone),
93
+ "data-tone": tone,
94
+ viewBox: "0 0 10 10",
95
+ refX: "9",
96
+ refY: "5",
97
+ markerWidth: "8",
98
+ markerHeight: "8",
99
+ orient: "auto-start-reverse",
100
+ markerUnits: "userSpaceOnUse",
101
+ children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z" })
102
+ },
103
+ tone
104
+ )) }) });
105
+ }
106
+ function EdgeLabelEditor({ initial, source, target }) {
107
+ const { messages, commitEditing, cancelEditing } = useCanvasContext();
108
+ const [draft, setDraft] = useState(initial);
109
+ const finished = useRef(false);
110
+ const field = useRef(null);
111
+ useEffect(() => {
112
+ field.current?.focus();
113
+ field.current?.select();
114
+ }, []);
115
+ const finish = (keep) => {
116
+ if (finished.current) return;
117
+ finished.current = true;
118
+ if (keep) commitEditing(draft);
119
+ else cancelEditing();
120
+ };
121
+ return /* @__PURE__ */ jsx(
122
+ "input",
123
+ {
124
+ ref: field,
125
+ type: "text",
126
+ dir: "auto",
127
+ "data-slot": "editor",
128
+ className: "nodrag nopan nowheel",
129
+ value: draft,
130
+ "aria-label": formatMessage(messages.edge.editLabel, { source, target }),
131
+ onChange: (event) => setDraft(event.target.value),
132
+ onBlur: () => finish(true),
133
+ onPointerDown: (event) => event.stopPropagation(),
134
+ onDoubleClick: (event) => event.stopPropagation(),
135
+ onKeyDown: (event) => {
136
+ event.stopPropagation();
137
+ if (event.nativeEvent.isComposing) return;
138
+ if (event.key === "Enter") {
139
+ event.preventDefault();
140
+ finish(true);
141
+ } else if (event.key === "Escape") {
142
+ event.preventDefault();
143
+ finish(false);
144
+ }
145
+ }
146
+ }
147
+ );
148
+ }
149
+ function KitEdgeComponent(props) {
150
+ const { id, source, target, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data, selected } = props;
151
+ const ctx = useCanvasContext();
152
+ if (!data) return null;
153
+ const { edge, tone, loopSize, sourceLabel, targetLabel } = data;
154
+ const lineKind = edge.kind ?? ctx.edgeKind;
155
+ const geometry = computeEdgePath({
156
+ source: { x: sourceX, y: sourceY },
157
+ target: { x: targetX, y: targetY },
158
+ sourceSide: sourcePosition,
159
+ targetSide: targetPosition,
160
+ ...lineKind === void 0 ? {} : { kind: lineKind },
161
+ ...source === target ? { loopSize } : {}
162
+ });
163
+ const marker = `url(#${ctx.markerId(tone)})`;
164
+ const editing = ctx.editing?.kind === "edge" && ctx.editing.id === id;
165
+ const hasLabel = edge.label !== void 0 && edge.label !== "";
166
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
167
+ /* @__PURE__ */ jsx(
168
+ BaseEdge,
169
+ {
170
+ id,
171
+ path: geometry.path,
172
+ markerEnd: marker,
173
+ ...edge.bidirectional ? { markerStart: marker } : {},
174
+ interactionWidth: 24,
175
+ "data-kit": "diagram-edge",
176
+ "data-tone": tone === "neutral" ? void 0 : tone,
177
+ "data-dashed": edge.dashed ? "" : void 0,
178
+ "data-animated": edge.animated ? "" : void 0,
179
+ "data-selected": selected ? "" : void 0
180
+ }
181
+ ),
182
+ hasLabel || editing ? /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(
183
+ "div",
184
+ {
185
+ "data-kit": "diagram-edge-label",
186
+ "data-tone": tone === "neutral" ? void 0 : tone,
187
+ "data-selected": selected ? "" : void 0,
188
+ "data-editing": editing ? "" : void 0,
189
+ className: "nodrag nopan",
190
+ dir: ctx.rtl ? "rtl" : "ltr",
191
+ style: { transform: `translate(-50%, -50%) translate(${geometry.label.x}px, ${geometry.label.y}px)` },
192
+ children: editing ? /* @__PURE__ */ jsx(EdgeLabelEditor, { initial: edge.label ?? "", source: sourceLabel, target: targetLabel }) : /* @__PURE__ */ jsx("span", { dir: "auto", children: edge.label })
193
+ }
194
+ ) }) : null
195
+ ] });
196
+ }
197
+ var KitEdge = memo(KitEdgeComponent);
198
+
199
+ // src/react/flow-adapter.ts
200
+ function displayLabel(node, messages) {
201
+ return node.label !== "" ? node.label : formatMessage(messages.kinds[node.kind]);
202
+ }
203
+ function buildFlowModel(doc, options) {
204
+ const { direction, rtl, editable, messages } = options;
205
+ const index = buildIndex(doc);
206
+ const summaries = summarizeNodes(doc);
207
+ const order = getReadingOrder(doc, { direction, rtl });
208
+ const hidden = /* @__PURE__ */ new Set();
209
+ for (const node of doc.nodes) {
210
+ if (getAncestors(index, node.id).some((id) => index.nodesById.get(id)?.collapsed === true)) hidden.add(node.id);
211
+ }
212
+ const nodes = [];
213
+ for (const id of order) {
214
+ const node = index.nodesById.get(id);
215
+ if (!node) continue;
216
+ const summary = summaries.get(id);
217
+ const size = getNodeSize(node);
218
+ const label = displayLabel(node, messages);
219
+ const parts = [
220
+ formatMessage(messages.node.label, {
221
+ label,
222
+ kind: formatMessage(messages.kinds[node.kind]),
223
+ incoming: summary?.incoming ?? 0,
224
+ outgoing: summary?.outgoing ?? 0
225
+ })
226
+ ];
227
+ if (node.status !== void 0 && node.status !== "neutral") {
228
+ parts.push(formatMessage(messages.node.status, { status: formatMessage(messages.statuses[node.status]) }));
229
+ }
230
+ if (node.description) parts.push(node.description);
231
+ if (node.locked) parts.push(formatMessage(messages.announce.locked, { label }));
232
+ const interactive = editable && node.locked !== true;
233
+ const parentId = node.parentId !== void 0 && node.parentId !== node.id && index.nodesById.has(node.parentId) ? node.parentId : void 0;
234
+ const flowNode = {
235
+ id,
236
+ type: "kit",
237
+ position: node.position,
238
+ data: {
239
+ node,
240
+ ports: getPortPlacements(node, direction, rtl),
241
+ childCount: index.children.get(id)?.length ?? 0
242
+ },
243
+ width: size.width,
244
+ height: size.height,
245
+ hidden: hidden.has(id),
246
+ draggable: interactive,
247
+ connectable: interactive,
248
+ deletable: interactive,
249
+ selectable: true,
250
+ focusable: true,
251
+ ariaLabel: parts.join(" "),
252
+ domAttributes: { "aria-roledescription": formatMessage(messages.node.roleDescription) },
253
+ ...parentId === void 0 ? {} : { parentId, extent: "parent" }
254
+ };
255
+ nodes.push(flowNode);
256
+ }
257
+ const labelOf = (id) => {
258
+ const node = index.nodesById.get(id);
259
+ return node ? displayLabel(node, messages) : id;
260
+ };
261
+ const edges = [];
262
+ for (const edge of doc.edges) {
263
+ const source = index.nodesById.get(edge.source);
264
+ const target = index.nodesById.get(edge.target);
265
+ if (!source || !target) continue;
266
+ const sourceLabel = labelOf(edge.source);
267
+ const targetLabel = labelOf(edge.target);
268
+ const values = { source: sourceLabel, target: targetLabel };
269
+ const size = getNodeSize(source);
270
+ const vertical = direction === "TB" || direction === "BT";
271
+ const ariaLabel = edge.label !== void 0 && edge.label !== "" ? formatMessage(messages.edge.labelWithText, { ...values, label: edge.label }) : formatMessage(messages.edge.label, values);
272
+ const flowEdge = {
273
+ id: edge.id,
274
+ source: edge.source,
275
+ target: edge.target,
276
+ type: "kit",
277
+ data: {
278
+ edge,
279
+ tone: edge.status ?? "neutral",
280
+ loopSize: Math.ceil((vertical ? size.width : size.height) / 2 / 0.75) + 24,
281
+ sourceLabel,
282
+ targetLabel
283
+ },
284
+ hidden: hidden.has(edge.source) || hidden.has(edge.target),
285
+ selectable: true,
286
+ focusable: options.edgesFocusable,
287
+ deletable: editable,
288
+ ariaLabel,
289
+ domAttributes: { "aria-roledescription": formatMessage(messages.edge.roleDescription) },
290
+ ...edge.sourcePort === void 0 ? {} : { sourceHandle: edge.sourcePort },
291
+ ...edge.targetPort === void 0 ? {} : { targetHandle: edge.targetPort }
292
+ };
293
+ edges.push(flowEdge);
294
+ }
295
+ return { nodes, edges, order, hidden, index };
296
+ }
297
+
298
+ // src/react/nodes.tsx
299
+ import { Badge, IconButton } from "@x12i/ui";
300
+ import { Handle, NodeResizer, Position } from "@xyflow/react";
301
+ import { memo as memo2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
302
+
303
+ // src/react/icons.tsx
304
+ import { Icon } from "@x12i/icons";
305
+ import { cssVar } from "@x12i/tokens";
306
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
307
+ function Glyph({ children, ...props }) {
308
+ return /* @__PURE__ */ jsx2(
309
+ "svg",
310
+ {
311
+ "data-kit": "diagram-icon",
312
+ viewBox: "0 0 24 24",
313
+ fill: "none",
314
+ stroke: "currentColor",
315
+ strokeLinecap: "round",
316
+ strokeLinejoin: "round",
317
+ "aria-hidden": "true",
318
+ focusable: "false",
319
+ style: { strokeWidth: cssVar("icon.strokeWidth") },
320
+ ...props,
321
+ children
322
+ }
323
+ );
324
+ }
325
+ var PATHS = {
326
+ undo: /* @__PURE__ */ jsxs2(Fragment2, { children: [
327
+ /* @__PURE__ */ jsx2("path", { d: "M9 14 4 9l5-5" }),
328
+ /* @__PURE__ */ jsx2("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" })
329
+ ] }),
330
+ redo: /* @__PURE__ */ jsxs2(Fragment2, { children: [
331
+ /* @__PURE__ */ jsx2("path", { d: "m15 14 5-5-5-5" }),
332
+ /* @__PURE__ */ jsx2("path", { d: "M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13" })
333
+ ] }),
334
+ fit: /* @__PURE__ */ jsxs2(Fragment2, { children: [
335
+ /* @__PURE__ */ jsx2("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
336
+ /* @__PURE__ */ jsx2("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
337
+ /* @__PURE__ */ jsx2("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
338
+ /* @__PURE__ */ jsx2("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
339
+ ] }),
340
+ layout: /* @__PURE__ */ jsxs2(Fragment2, { children: [
341
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "3", width: "8", height: "8", rx: "2" }),
342
+ /* @__PURE__ */ jsx2("path", { d: "M7 11v4a2 2 0 0 0 2 2h4" }),
343
+ /* @__PURE__ */ jsx2("rect", { x: "13", y: "13", width: "8", height: "8", rx: "2" })
344
+ ] }),
345
+ group: /* @__PURE__ */ jsxs2(Fragment2, { children: [
346
+ /* @__PURE__ */ jsx2("path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }),
347
+ /* @__PURE__ */ jsx2("path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }),
348
+ /* @__PURE__ */ jsx2("path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }),
349
+ /* @__PURE__ */ jsx2("path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }),
350
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "7", width: "7", height: "5", rx: "1" }),
351
+ /* @__PURE__ */ jsx2("rect", { x: "10", y: "12", width: "7", height: "5", rx: "1" })
352
+ ] }),
353
+ ungroup: /* @__PURE__ */ jsxs2(Fragment2, { children: [
354
+ /* @__PURE__ */ jsx2("rect", { x: "5", y: "4", width: "8", height: "6", rx: "1" }),
355
+ /* @__PURE__ */ jsx2("rect", { x: "11", y: "14", width: "8", height: "6", rx: "1" })
356
+ ] }),
357
+ map: /* @__PURE__ */ jsxs2(Fragment2, { children: [
358
+ /* @__PURE__ */ jsx2("path", { d: "M14.1 5.55a2 2 0 0 0 1.79 0l3.66-1.83A1 1 0 0 1 21 4.62v12.76a1 1 0 0 1-.55.89l-4.55 2.28a2 2 0 0 1-1.79 0l-4.21-2.1a2 2 0 0 0-1.79 0l-3.66 1.83A1 1 0 0 1 3 19.38V6.62a1 1 0 0 1 .55-.9l4.55-2.27a2 2 0 0 1 1.79 0z" }),
359
+ /* @__PURE__ */ jsx2("path", { d: "M15 5.76v15" }),
360
+ /* @__PURE__ */ jsx2("path", { d: "M9 3.24v15" })
361
+ ] }),
362
+ grid: /* @__PURE__ */ jsxs2(Fragment2, { children: [
363
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
364
+ /* @__PURE__ */ jsx2("path", { d: "M3 9h18" }),
365
+ /* @__PURE__ */ jsx2("path", { d: "M3 15h18" }),
366
+ /* @__PURE__ */ jsx2("path", { d: "M9 3v18" }),
367
+ /* @__PURE__ */ jsx2("path", { d: "M15 3v18" })
368
+ ] }),
369
+ download: /* @__PURE__ */ jsxs2(Fragment2, { children: [
370
+ /* @__PURE__ */ jsx2("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
371
+ /* @__PURE__ */ jsx2("path", { d: "m7 10 5 5 5-5" }),
372
+ /* @__PURE__ */ jsx2("path", { d: "M12 15V3" })
373
+ ] })
374
+ };
375
+ function ToolIcon({ name, ...props }) {
376
+ switch (name) {
377
+ case "plus":
378
+ case "minus":
379
+ case "trash":
380
+ case "copy":
381
+ case "chevron-down":
382
+ case "chevron-right":
383
+ return /* @__PURE__ */ jsx2(Icon, { name, size: 16, "data-kit": "diagram-icon" });
384
+ default:
385
+ return /* @__PURE__ */ jsx2(Glyph, { ...props, children: PATHS[name] });
386
+ }
387
+ }
388
+ var KIND_PATHS = {
389
+ start: /* @__PURE__ */ jsx2("rect", { x: "3", y: "8", width: "18", height: "8", rx: "4" }),
390
+ end: /* @__PURE__ */ jsxs2(Fragment2, { children: [
391
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "8", width: "18", height: "8", rx: "4" }),
392
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "11", width: "10", height: "2", rx: "1" })
393
+ ] }),
394
+ task: /* @__PURE__ */ jsx2("rect", { x: "3", y: "7", width: "18", height: "10", rx: "2" }),
395
+ decision: /* @__PURE__ */ jsx2("path", { d: "M12 3 21 12 12 21 3 12z" }),
396
+ group: /* @__PURE__ */ jsx2("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2", strokeDasharray: "3 3" }),
397
+ subflow: /* @__PURE__ */ jsxs2(Fragment2, { children: [
398
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
399
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "10", width: "10", height: "6", rx: "1" })
400
+ ] }),
401
+ note: /* @__PURE__ */ jsxs2(Fragment2, { children: [
402
+ /* @__PURE__ */ jsx2("path", { d: "M5 4h10l4 4v12H5z" }),
403
+ /* @__PURE__ */ jsx2("path", { d: "M15 4v4h4" })
404
+ ] }),
405
+ image: /* @__PURE__ */ jsxs2(Fragment2, { children: [
406
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
407
+ /* @__PURE__ */ jsx2("path", { d: "m3 16 5-5 4 4 3-3 6 6" })
408
+ ] }),
409
+ entity: /* @__PURE__ */ jsxs2(Fragment2, { children: [
410
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
411
+ /* @__PURE__ */ jsx2("path", { d: "M3 9h18" }),
412
+ /* @__PURE__ */ jsx2("path", { d: "M8 13h8" }),
413
+ /* @__PURE__ */ jsx2("path", { d: "M8 16.5h5" })
414
+ ] }),
415
+ code: /* @__PURE__ */ jsxs2(Fragment2, { children: [
416
+ /* @__PURE__ */ jsx2("path", { d: "m8 8-4 4 4 4" }),
417
+ /* @__PURE__ */ jsx2("path", { d: "m16 8 4 4-4 4" }),
418
+ /* @__PURE__ */ jsx2("path", { d: "m14 5-4 14" })
419
+ ] }),
420
+ chart: /* @__PURE__ */ jsxs2(Fragment2, { children: [
421
+ /* @__PURE__ */ jsx2("path", { d: "M4 20V4" }),
422
+ /* @__PURE__ */ jsx2("path", { d: "M4 20h16" }),
423
+ /* @__PURE__ */ jsx2("path", { d: "M8 16v-5" }),
424
+ /* @__PURE__ */ jsx2("path", { d: "M12 16V7" }),
425
+ /* @__PURE__ */ jsx2("path", { d: "M16 16v-3" })
426
+ ] }),
427
+ agent: /* @__PURE__ */ jsxs2(Fragment2, { children: [
428
+ /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "8", r: "4" }),
429
+ /* @__PURE__ */ jsx2("path", { d: "M4 21a8 8 0 0 1 16 0" })
430
+ ] })
431
+ };
432
+ function KindIcon({ kind, ...props }) {
433
+ return /* @__PURE__ */ jsx2(Glyph, { ...props, children: KIND_PATHS[kind] });
434
+ }
435
+
436
+ // src/react/nodes.tsx
437
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
438
+ var SIDE = {
439
+ top: Position.Top,
440
+ right: Position.Right,
441
+ bottom: Position.Bottom,
442
+ left: Position.Left
443
+ };
444
+ var RESIZABLE = /* @__PURE__ */ new Set(["group", "subflow", "note", "image", "chart", "code", "entity"]);
445
+ var MIN_SIZE = { width: 96, height: 64 };
446
+ var MIN_CONTAINER_SIZE = { width: 160, height: 96 };
447
+ function isComposing(event) {
448
+ return event.nativeEvent.isComposing || event.keyCode === 229;
449
+ }
450
+ function LabelEditor({ node, multiline }) {
451
+ const { messages, commitEditing, cancelEditing } = useCanvasContext();
452
+ const [draft, setDraft] = useState2(node.label);
453
+ const finished = useRef2(false);
454
+ const field = useRef2(null);
455
+ useEffect2(() => {
456
+ field.current?.focus();
457
+ field.current?.select();
458
+ }, []);
459
+ const finish = (keep) => {
460
+ if (finished.current) return;
461
+ finished.current = true;
462
+ if (keep) commitEditing(draft);
463
+ else cancelEditing();
464
+ };
465
+ const props = {
466
+ ref: field,
467
+ value: draft,
468
+ dir: "auto",
469
+ className: "nodrag nopan nowheel",
470
+ "data-slot": "editor",
471
+ "aria-label": formatMessage(messages.node.editLabel, {
472
+ label: node.label !== "" ? node.label : formatMessage(messages.kinds[node.kind])
473
+ }),
474
+ onChange: (event) => setDraft(event.target.value),
475
+ onBlur: () => finish(true),
476
+ onKeyDown: (event) => {
477
+ event.stopPropagation();
478
+ if (isComposing(event)) return;
479
+ if (event.key === "Enter" && !(multiline && event.shiftKey)) {
480
+ event.preventDefault();
481
+ finish(true);
482
+ } else if (event.key === "Escape") {
483
+ event.preventDefault();
484
+ finish(false);
485
+ }
486
+ },
487
+ onPointerDown: (event) => event.stopPropagation(),
488
+ onDoubleClick: (event) => event.stopPropagation()
489
+ };
490
+ return multiline ? /* @__PURE__ */ jsx3("textarea", { rows: 2, ...props }) : /* @__PURE__ */ jsx3("input", { type: "text", ...props });
491
+ }
492
+ function fieldsOf(node) {
493
+ const raw = node.data?.fields;
494
+ if (!Array.isArray(raw)) return [];
495
+ return raw.map((field) => {
496
+ if (typeof field === "string") return { name: field };
497
+ if (typeof field === "object" && field !== null) {
498
+ const record = field;
499
+ const result = { name: String(record.name ?? "") };
500
+ if (typeof record.type === "string" && record.type !== "") result.type = record.type;
501
+ if (record.key === true) result.key = true;
502
+ return result;
503
+ }
504
+ return { name: String(field) };
505
+ });
506
+ }
507
+ function numbersOf(node) {
508
+ const raw = node.data?.values;
509
+ if (!Array.isArray(raw)) return [];
510
+ return raw.filter((value) => typeof value === "number" && Number.isFinite(value)).slice(0, 12);
511
+ }
512
+ function stringOf(node, key) {
513
+ const value = node.data?.[key];
514
+ return typeof value === "string" && value !== "" ? value : void 0;
515
+ }
516
+ function ChartBars({ values }) {
517
+ const shown = values.length > 0 ? values : [4, 7, 5.5, 9];
518
+ const max = Math.max(...shown, 1);
519
+ const width = 100 / shown.length;
520
+ return /* @__PURE__ */ jsx3("svg", { "data-slot": "chart", viewBox: "0 0 100 60", preserveAspectRatio: "none", "aria-hidden": "true", focusable: "false", children: shown.map((value, i) => {
521
+ const height = Math.max(2, Math.max(0, value) / max * 56);
522
+ return /* @__PURE__ */ jsx3("rect", { x: i * width + width * 0.15, y: 60 - height, width: width * 0.7, height }, i);
523
+ }) });
524
+ }
525
+ function DefaultBody({ node, label, childCount }) {
526
+ const { messages, editable, toggleCollapsed } = useCanvasContext();
527
+ switch (node.kind) {
528
+ case "decision":
529
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
530
+ /* @__PURE__ */ jsx3("svg", { "data-slot": "shape", viewBox: "0 0 100 100", preserveAspectRatio: "none", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx3("polygon", { points: "50,0 100,50 50,100 0,50", vectorEffect: "non-scaling-stroke" }) }),
531
+ /* @__PURE__ */ jsx3("div", { "data-slot": "content", children: label })
532
+ ] });
533
+ case "group":
534
+ case "subflow": {
535
+ const collapsed = node.collapsed === true;
536
+ const name = node.label !== "" ? node.label : formatMessage(messages.kinds[node.kind]);
537
+ return /* @__PURE__ */ jsxs3("div", { "data-slot": "header", children: [
538
+ /* @__PURE__ */ jsx3(
539
+ IconButton,
540
+ {
541
+ label: formatMessage(collapsed ? messages.node.expand : messages.node.collapse, { label: name }),
542
+ variant: "ghost",
543
+ tone: "neutral",
544
+ size: "xs",
545
+ className: "nodrag nopan",
546
+ "aria-expanded": !collapsed,
547
+ onClick: (event) => {
548
+ event.stopPropagation();
549
+ toggleCollapsed(node.id);
550
+ },
551
+ onDoubleClick: (event) => event.stopPropagation(),
552
+ children: /* @__PURE__ */ jsx3("span", { "data-mirror": "", children: /* @__PURE__ */ jsx3(ToolIcon, { name: collapsed ? "chevron-right" : "chevron-down" }) })
553
+ }
554
+ ),
555
+ label,
556
+ collapsed ? /* @__PURE__ */ jsx3("span", { "data-slot": "count", children: formatMessage(messages.node.childCount, { count: childCount }) }) : null
557
+ ] });
558
+ }
559
+ case "entity": {
560
+ const fields = fieldsOf(node);
561
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
562
+ /* @__PURE__ */ jsx3("div", { "data-slot": "header", children: label }),
563
+ /* @__PURE__ */ jsx3("ul", { "data-slot": "fields", children: fields.map((field, i) => /* @__PURE__ */ jsxs3("li", { "data-key": field.key ? "" : void 0, children: [
564
+ /* @__PURE__ */ jsx3("span", { dir: "auto", children: field.name }),
565
+ field.type === void 0 ? null : /* @__PURE__ */ jsx3("span", { "data-slot": "type", children: field.type })
566
+ ] }, i)) })
567
+ ] });
568
+ }
569
+ case "code": {
570
+ const code = stringOf(node, "code") ?? "";
571
+ const language = stringOf(node, "language");
572
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
573
+ /* @__PURE__ */ jsxs3("div", { "data-slot": "header", children: [
574
+ label,
575
+ language === void 0 ? null : /* @__PURE__ */ jsx3("span", { "data-slot": "type", children: language })
576
+ ] }),
577
+ /* @__PURE__ */ jsx3("pre", { dir: "ltr", "data-slot": "code", children: /* @__PURE__ */ jsx3("code", { children: code.split(/\r?\n/).slice(0, 12).join("\n") }) })
578
+ ] });
579
+ }
580
+ case "image": {
581
+ const src = node.data?.src;
582
+ const alt = stringOf(node, "alt") ?? "";
583
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
584
+ /* @__PURE__ */ jsx3("div", { "data-slot": "media", children: isSafeImageUrl(src) ? /* @__PURE__ */ jsx3("img", { src, alt, loading: "lazy", decoding: "async", draggable: false, referrerPolicy: "no-referrer" }) : /* @__PURE__ */ jsx3(KindIcon, { kind: "image" }) }),
585
+ /* @__PURE__ */ jsx3("div", { "data-slot": "caption", children: label })
586
+ ] });
587
+ }
588
+ case "chart":
589
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
590
+ /* @__PURE__ */ jsx3(ChartBars, { values: numbersOf(node) }),
591
+ /* @__PURE__ */ jsx3("div", { "data-slot": "caption", children: label })
592
+ ] });
593
+ case "agent": {
594
+ const role = stringOf(node, "role");
595
+ const initial = Array.from(node.label.trim())[0]?.toUpperCase() ?? "";
596
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
597
+ /* @__PURE__ */ jsx3("span", { "data-slot": "avatar", "aria-hidden": "true", children: initial }),
598
+ /* @__PURE__ */ jsxs3("div", { "data-slot": "content", children: [
599
+ label,
600
+ role === void 0 ? null : /* @__PURE__ */ jsx3("span", { "data-slot": "role", dir: "auto", children: role })
601
+ ] })
602
+ ] });
603
+ }
604
+ default:
605
+ return /* @__PURE__ */ jsx3("div", { "data-slot": "content", "data-editable": editable ? "" : void 0, children: label });
606
+ }
607
+ }
608
+ function KitNodeComponent({ id, data, selected }) {
609
+ const ctx = useCanvasContext();
610
+ const { node, ports, childCount } = data;
611
+ const { messages } = ctx;
612
+ const editing = ctx.editing?.kind === "node" && ctx.editing.id === id;
613
+ const interactive = ctx.editable && node.locked !== true;
614
+ const kindName = formatMessage(messages.kinds[node.kind]);
615
+ const collapsed = node.collapsed === true && isContainerKind(node.kind);
616
+ const label = editing ? /* @__PURE__ */ jsx3(LabelEditor, { node, multiline: node.kind === "note" }) : /* @__PURE__ */ jsx3("span", { "data-slot": "label", dir: "auto", children: node.label !== "" ? node.label : /* @__PURE__ */ jsx3("span", { "data-placeholder": "", children: kindName }) });
617
+ const defaultBody = /* @__PURE__ */ jsx3(DefaultBody, { node, label, childCount });
618
+ const custom = ctx.renderers[node.kind]?.({
619
+ node,
620
+ selected,
621
+ editing,
622
+ rtl: ctx.rtl,
623
+ direction: ctx.direction,
624
+ messages,
625
+ label,
626
+ defaultBody
627
+ });
628
+ const body = custom === void 0 ? defaultBody : custom;
629
+ const minimum = isContainerKind(node.kind) ? MIN_CONTAINER_SIZE : MIN_SIZE;
630
+ const status = node.status !== void 0 && node.status !== "neutral" ? node.status : void 0;
631
+ return /* @__PURE__ */ jsxs3(
632
+ "div",
633
+ {
634
+ "data-kit": "diagram-node",
635
+ "data-kind": node.kind,
636
+ "data-status": status,
637
+ "data-selected": selected ? "" : void 0,
638
+ "data-editing": editing ? "" : void 0,
639
+ "data-locked": node.locked ? "" : void 0,
640
+ "data-collapsed": collapsed ? "" : void 0,
641
+ "data-connecting": ctx.connectingFrom === id ? "" : void 0,
642
+ dir: ctx.rtl ? "rtl" : "ltr",
643
+ children: [
644
+ RESIZABLE.has(node.kind) ? /* @__PURE__ */ jsx3(
645
+ NodeResizer,
646
+ {
647
+ isVisible: selected && interactive && !collapsed,
648
+ minWidth: minimum.width,
649
+ minHeight: minimum.height,
650
+ onResizeEnd: (_event, params) => ctx.commitResize(id, params)
651
+ }
652
+ ) : null,
653
+ ports.map((port) => {
654
+ const horizontal = port.side === "left" || port.side === "right";
655
+ const offset = `${Math.round(port.offset * 1e4) / 100}%`;
656
+ return /* @__PURE__ */ jsx3(
657
+ Handle,
658
+ {
659
+ id: port.id ?? null,
660
+ type: port.direction === "in" ? "target" : "source",
661
+ position: SIDE[port.side],
662
+ isConnectable: interactive,
663
+ "data-kit": "diagram-handle",
664
+ "data-port-type": port.type,
665
+ style: horizontal ? { top: offset } : { left: offset }
666
+ },
667
+ `${port.direction}:${port.id ?? ""}`
668
+ );
669
+ }),
670
+ body,
671
+ status === void 0 ? null : /* @__PURE__ */ jsx3(Badge, { tone: status, size: "sm", "data-slot": "status", children: formatMessage(messages.statuses[status]) })
672
+ ]
673
+ }
674
+ );
675
+ }
676
+ var KitNode = memo2(KitNodeComponent);
677
+
678
+ // src/react/outline.tsx
679
+ import { createElement, useId, useMemo } from "react";
680
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
681
+ function focusDiagramNode(canvasId, nodeId) {
682
+ const root = typeof document === "undefined" ? null : document.getElementById(canvasId);
683
+ const element = root ? Array.from(root.querySelectorAll(".react-flow__node")).find((candidate) => candidate.dataset.id === nodeId) : void 0;
684
+ if (!element) return false;
685
+ element.focus();
686
+ return true;
687
+ }
688
+ function DiagramOutline({
689
+ document: doc,
690
+ messages: messagesOverride,
691
+ direction,
692
+ rtl = false,
693
+ canvasId,
694
+ canvas,
695
+ onNavigate,
696
+ onDisconnect,
697
+ headingLevel = 3,
698
+ reveal = "always",
699
+ showPaths = true,
700
+ className,
701
+ ...props
702
+ }) {
703
+ const messages = useMemo(() => resolveMessages(messagesOverride), [messagesOverride]);
704
+ const headingId = useId();
705
+ const tree = useMemo(
706
+ () => diagramToOutlineTree(doc, { rtl, ...direction === void 0 ? {} : { direction } }),
707
+ [doc, direction, rtl]
708
+ );
709
+ const paths = useMemo(
710
+ () => showPaths ? diagramToOutline(doc, { connector: formatMessage(messages.outline.connector), maxPaths: 50 }) : [],
711
+ [doc, messages, showPaths]
712
+ );
713
+ const navigable = canvasId !== void 0 || canvas !== void 0 || onNavigate !== void 0;
714
+ const kindName = (item) => formatMessage(messages.kinds[item.kind]);
715
+ const go = (nodeId) => {
716
+ onNavigate?.(nodeId);
717
+ if (canvas?.current) canvas.current.focusNode(nodeId);
718
+ else if (canvasId !== void 0) focusDiagramNode(canvasId, nodeId);
719
+ };
720
+ const link = (nodeId, text) => navigable ? /* @__PURE__ */ jsx4("button", { type: "button", "data-slot": "link", "aria-label": formatMessage(messages.outline.goToNode, { label: text }), onClick: () => go(nodeId), children: /* @__PURE__ */ jsx4("span", { dir: "auto", children: text }) }) : /* @__PURE__ */ jsx4("span", { dir: "auto", children: text });
721
+ const renderItem = (item) => {
722
+ const labelText = item.label !== "" ? item.label : kindName(item);
723
+ const meta = [kindName(item)];
724
+ if (item.status !== void 0 && item.status !== "neutral") {
725
+ meta.push(formatMessage(messages.outline.status, { status: formatMessage(messages.statuses[item.status]) }));
726
+ }
727
+ const connections = [
728
+ ...item.outgoing.map((connection) => ({ connection, kind: "out" })),
729
+ ...item.incoming.map((connection) => ({ connection, kind: "in" }))
730
+ ];
731
+ return /* @__PURE__ */ jsxs4("li", { "data-kind": item.kind, "data-status": item.status === void 0 || item.status === "neutral" ? void 0 : item.status, children: [
732
+ /* @__PURE__ */ jsxs4("div", { "data-slot": "item", children: [
733
+ link(item.id, labelText),
734
+ /* @__PURE__ */ jsx4("span", { "data-slot": "meta", children: meta.join(". ") })
735
+ ] }),
736
+ item.description ? /* @__PURE__ */ jsx4("p", { "data-slot": "description", dir: "auto", children: item.description }) : null,
737
+ connections.length === 0 ? /* @__PURE__ */ jsx4("p", { "data-slot": "none", children: formatMessage(messages.outline.noConnections) }) : /* @__PURE__ */ jsx4("ul", { "data-slot": "connections", children: connections.map(({ connection, kind }) => {
738
+ const source = kind === "out" ? labelText : connection.label;
739
+ const target = kind === "out" ? connection.label : labelText;
740
+ return /* @__PURE__ */ jsxs4("li", { "data-direction": kind, children: [
741
+ /* @__PURE__ */ jsx4("span", { "data-slot": "relation", children: formatMessage(kind === "out" ? messages.outline.connectsTo : messages.outline.receivesFrom) }),
742
+ " ",
743
+ link(connection.nodeId, connection.label),
744
+ connection.edgeLabel === void 0 ? null : /* @__PURE__ */ jsxs4("span", { "data-slot": "edge-label", dir: "auto", children: [
745
+ " ",
746
+ "(",
747
+ connection.edgeLabel,
748
+ ")"
749
+ ] }),
750
+ onDisconnect && kind === "out" ? /* @__PURE__ */ jsx4(
751
+ "button",
752
+ {
753
+ type: "button",
754
+ "data-slot": "disconnect",
755
+ "aria-label": formatMessage(messages.outline.disconnect, { source, target }),
756
+ onClick: () => onDisconnect(connection.edgeId),
757
+ children: /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "\xD7" })
758
+ }
759
+ ) : null
760
+ ] }, `${kind}:${connection.edgeId}`);
761
+ }) }),
762
+ item.children.length > 0 ? /* @__PURE__ */ jsxs4("div", { "data-slot": "children", children: [
763
+ /* @__PURE__ */ jsx4("p", { "data-slot": "contains", children: formatMessage(messages.outline.contains, { label: labelText }) }),
764
+ /* @__PURE__ */ jsx4("ul", { children: item.children.map(renderItem) })
765
+ ] }) : null
766
+ ] }, item.id);
767
+ };
768
+ return /* @__PURE__ */ jsxs4(
769
+ "section",
770
+ {
771
+ "data-kit": "diagram-outline",
772
+ "data-reveal": reveal,
773
+ "aria-labelledby": headingId,
774
+ dir: rtl ? "rtl" : void 0,
775
+ className,
776
+ ...props,
777
+ children: [
778
+ createElement(`h${headingLevel}`, { id: headingId, "data-slot": "title" }, formatMessage(messages.outline.title)),
779
+ doc.description ? /* @__PURE__ */ jsx4("p", { "data-slot": "summary", dir: "auto", children: doc.description }) : null,
780
+ tree.length === 0 ? /* @__PURE__ */ jsx4("p", { "data-slot": "empty", children: formatMessage(messages.outline.empty) }) : /* @__PURE__ */ jsx4("ul", { "data-slot": "nodes", "aria-label": formatMessage(messages.outline.nodes), children: tree.map(renderItem) }),
781
+ paths.length > 0 ? /* @__PURE__ */ jsxs4("div", { "data-slot": "paths", children: [
782
+ /* @__PURE__ */ jsx4("p", { "data-slot": "paths-title", children: formatMessage(messages.outline.paths) }),
783
+ /* @__PURE__ */ jsx4("ul", { "aria-label": formatMessage(messages.outline.paths), children: paths.map((path, i) => /* @__PURE__ */ jsx4("li", { dir: "auto", children: path }, i)) })
784
+ ] }) : null
785
+ ]
786
+ }
787
+ );
788
+ }
789
+
790
+ // src/react/toolbar.tsx
791
+ import { Button, Divider, IconButton as IconButton2 } from "@x12i/ui";
792
+ import { useId as useId2, useRef as useRef3 } from "react";
793
+ import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
794
+ var PALETTE_DRAG_TYPE = "application/x-kit-diagram-node";
795
+ function Tool({
796
+ icon,
797
+ label,
798
+ onClick,
799
+ disabled,
800
+ pressed,
801
+ expanded,
802
+ controls
803
+ }) {
804
+ return /* @__PURE__ */ jsx5(
805
+ IconButton2,
806
+ {
807
+ label,
808
+ variant: "ghost",
809
+ tone: "neutral",
810
+ size: "sm",
811
+ disabled: disabled ?? false,
812
+ "aria-pressed": pressed,
813
+ "aria-expanded": expanded,
814
+ "aria-controls": controls,
815
+ onClick,
816
+ children: /* @__PURE__ */ jsx5(ToolIcon, { name: icon })
817
+ }
818
+ );
819
+ }
820
+ function DiagramToolbar({ messages, editable, sections, palette, state, actions, rtl, paletteOpen, onPaletteOpenChange }) {
821
+ const t = (message) => formatMessage(message);
822
+ const root = useRef3(null);
823
+ const paletteId = useId2();
824
+ const showPalette = editable && sections.palette && palette.length > 0;
825
+ const showHistory = sections.history && editable;
826
+ const showEdit = editable && sections.edit;
827
+ const showLayout = editable && sections.layout;
828
+ const showView = sections.view;
829
+ const onKeyDown = (event) => {
830
+ const keys = ["ArrowLeft", "ArrowRight", "Home", "End"];
831
+ if (!keys.includes(event.key) || event.altKey || event.ctrlKey || event.metaKey) return;
832
+ const buttons = Array.from(root.current?.querySelectorAll("button:not([disabled]):not([aria-disabled='true'])") ?? []);
833
+ const current = buttons.indexOf(document.activeElement);
834
+ if (current === -1 || buttons.length === 0) return;
835
+ const forward = event.key === "ArrowRight" !== rtl;
836
+ const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (current + (forward ? 1 : -1) + buttons.length) % buttons.length;
837
+ event.preventDefault();
838
+ buttons[next]?.focus();
839
+ };
840
+ const groups = [];
841
+ if (showPalette) {
842
+ groups.push({
843
+ key: "palette",
844
+ content: /* @__PURE__ */ jsx5(
845
+ Tool,
846
+ {
847
+ icon: "plus",
848
+ label: t(messages.toolbar.addNode),
849
+ onClick: () => onPaletteOpenChange(!paletteOpen),
850
+ expanded: paletteOpen,
851
+ controls: paletteId
852
+ }
853
+ )
854
+ });
855
+ }
856
+ if (showHistory) {
857
+ groups.push({
858
+ key: "history",
859
+ content: /* @__PURE__ */ jsxs5(Fragment4, { children: [
860
+ /* @__PURE__ */ jsx5(Tool, { icon: "undo", label: t(messages.toolbar.undo), onClick: actions.undo, disabled: !state.canUndo }),
861
+ /* @__PURE__ */ jsx5(Tool, { icon: "redo", label: t(messages.toolbar.redo), onClick: actions.redo, disabled: !state.canRedo })
862
+ ] })
863
+ });
864
+ }
865
+ if (showEdit) {
866
+ groups.push({
867
+ key: "edit",
868
+ content: /* @__PURE__ */ jsxs5(Fragment4, { children: [
869
+ /* @__PURE__ */ jsx5(Tool, { icon: "trash", label: t(messages.toolbar.delete), onClick: actions.deleteSelection, disabled: !state.hasSelection }),
870
+ /* @__PURE__ */ jsx5(Tool, { icon: "copy", label: t(messages.toolbar.duplicate), onClick: actions.duplicate, disabled: !state.hasSelection }),
871
+ /* @__PURE__ */ jsx5(Tool, { icon: "group", label: t(messages.toolbar.group), onClick: actions.group, disabled: !state.canGroup }),
872
+ /* @__PURE__ */ jsx5(Tool, { icon: "ungroup", label: t(messages.toolbar.ungroup), onClick: actions.ungroup, disabled: !state.canUngroup })
873
+ ] })
874
+ });
875
+ }
876
+ if (showLayout) {
877
+ groups.push({
878
+ key: "layout",
879
+ content: /* @__PURE__ */ jsx5(Tool, { icon: "layout", label: t(messages.toolbar.autoLayout), onClick: actions.layout })
880
+ });
881
+ }
882
+ if (showView) {
883
+ groups.push({
884
+ key: "view",
885
+ content: /* @__PURE__ */ jsxs5(Fragment4, { children: [
886
+ /* @__PURE__ */ jsx5(Tool, { icon: "minus", label: t(messages.toolbar.zoomOut), onClick: actions.zoomOut }),
887
+ /* @__PURE__ */ jsx5(Tool, { icon: "plus", label: t(messages.toolbar.zoomIn), onClick: actions.zoomIn }),
888
+ /* @__PURE__ */ jsx5(Tool, { icon: "fit", label: t(messages.toolbar.fitView), onClick: actions.fitView }),
889
+ /* @__PURE__ */ jsx5(
890
+ Tool,
891
+ {
892
+ icon: "map",
893
+ label: t(state.minimap ? messages.toolbar.hideMiniMap : messages.toolbar.showMiniMap),
894
+ onClick: actions.toggleMiniMap,
895
+ pressed: state.minimap
896
+ }
897
+ ),
898
+ /* @__PURE__ */ jsx5(
899
+ Tool,
900
+ {
901
+ icon: "grid",
902
+ label: t(state.grid ? messages.toolbar.hideGrid : messages.toolbar.showGrid),
903
+ onClick: actions.toggleGrid,
904
+ pressed: state.grid
905
+ }
906
+ )
907
+ ] })
908
+ });
909
+ }
910
+ if (groups.length === 0) return null;
911
+ const onDragStart = (event, kind) => {
912
+ event.dataTransfer.setData(PALETTE_DRAG_TYPE, kind);
913
+ event.dataTransfer.effectAllowed = "copy";
914
+ };
915
+ return /* @__PURE__ */ jsxs5("div", { "data-kit": "diagram-toolbar-area", children: [
916
+ /* @__PURE__ */ jsx5("div", { ref: root, "data-kit": "diagram-toolbar", role: "toolbar", "aria-label": t(messages.toolbar.label), "aria-orientation": "horizontal", onKeyDown, children: groups.map((group, i) => /* @__PURE__ */ jsxs5("div", { "data-slot": "group", role: "presentation", children: [
917
+ i > 0 ? /* @__PURE__ */ jsx5(Divider, { orientation: "vertical", decorative: true }) : null,
918
+ group.content
919
+ ] }, group.key)) }),
920
+ showPalette && paletteOpen ? /* @__PURE__ */ jsx5("div", { id: paletteId, "data-kit": "diagram-palette", role: "group", "aria-label": t(messages.toolbar.palette), children: palette.map((kind) => /* @__PURE__ */ jsxs5(
921
+ Button,
922
+ {
923
+ variant: "outline",
924
+ tone: "neutral",
925
+ size: "sm",
926
+ draggable: true,
927
+ onDragStart: (event) => onDragStart(event, kind),
928
+ onClick: () => actions.addNode(kind),
929
+ children: [
930
+ /* @__PURE__ */ jsx5(KindIcon, { kind }),
931
+ t(messages.kinds[kind])
932
+ ]
933
+ },
934
+ kind
935
+ )) }) : null
936
+ ] });
937
+ }
938
+
939
+ // src/react/use-history.ts
940
+ import { useCallback, useMemo as useMemo2, useReducer } from "react";
941
+ function useDiagramHistory(initial, options = {}) {
942
+ const limit = options.limit;
943
+ const [state, dispatch] = useReducer(
944
+ (current, action) => {
945
+ const historyOptions = limit === void 0 ? {} : { limit };
946
+ switch (action.type) {
947
+ case "update":
948
+ return historyReducer(
949
+ current,
950
+ {
951
+ type: "set",
952
+ value: action.update(current.present),
953
+ ...action.coalesceKey === void 0 ? {} : { coalesceKey: action.coalesceKey }
954
+ },
955
+ historyOptions
956
+ );
957
+ case "undo":
958
+ return historyReducer(current, { type: "undo" }, historyOptions);
959
+ case "redo":
960
+ return historyReducer(current, { type: "redo" }, historyOptions);
961
+ case "reset":
962
+ return historyReducer(current, { type: "reset", value: action.value }, historyOptions);
963
+ case "clear":
964
+ return historyReducer(current, { type: "clear" }, historyOptions);
965
+ }
966
+ },
967
+ initial,
968
+ (value) => createHistory(typeof value === "function" ? value() : value)
969
+ );
970
+ const set = useCallback((next, meta) => {
971
+ dispatch({
972
+ type: "update",
973
+ update: typeof next === "function" ? next : () => next,
974
+ ...meta?.coalesceKey === void 0 ? {} : { coalesceKey: meta.coalesceKey }
975
+ });
976
+ }, []);
977
+ const undo = useCallback(() => dispatch({ type: "undo" }), []);
978
+ const redo = useCallback(() => dispatch({ type: "redo" }), []);
979
+ const reset = useCallback((value) => dispatch({ type: "reset", value }), []);
980
+ const clear = useCallback(() => dispatch({ type: "clear" }), []);
981
+ return useMemo2(
982
+ () => ({
983
+ document: state.present,
984
+ set,
985
+ undo,
986
+ redo,
987
+ canUndo: state.past.length > 0,
988
+ canRedo: state.future.length > 0,
989
+ reset,
990
+ clear,
991
+ undoDepth: state.past.length,
992
+ redoDepth: state.future.length
993
+ }),
994
+ [state, set, undo, redo, reset, clear]
995
+ );
996
+ }
997
+
998
+ // src/react/canvas.tsx
999
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1000
+ var NODE_TYPES = { kit: KitNode };
1001
+ var EDGE_TYPES = { kit: KitEdge };
1002
+ var FIT_OPTIONS = { padding: 0.12, duration: 0 };
1003
+ var NUDGE_FALLBACK = GRID_SIZE;
1004
+ var PASTE_STEP = 24;
1005
+ var NOTICE_MS = 6e3;
1006
+ var FOCUS_WATCH_MS = 1500;
1007
+ var ANIMATION_MS = 200;
1008
+ var ARROWS = { ArrowUp: "up", ArrowDown: "down", ArrowLeft: "left", ArrowRight: "right" };
1009
+ var clipboard;
1010
+ var NO_SELECTION = { nodes: /* @__PURE__ */ new Set(), edges: /* @__PURE__ */ new Set() };
1011
+ var NO_OVERLAY = { positions: {}, sizes: {} };
1012
+ function prefersReducedMotion() {
1013
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1014
+ }
1015
+ function isTextEntry(element) {
1016
+ return element.closest("input, textarea, select, [contenteditable='true'], [contenteditable='']") !== null;
1017
+ }
1018
+ function round(value) {
1019
+ return Math.round(value * 100) / 100;
1020
+ }
1021
+ function topLevel(ids, doc) {
1022
+ const parentOf = new Map(doc.nodes.map((node) => [node.id, node.parentId]));
1023
+ const result = [];
1024
+ for (const id of ids) {
1025
+ let parent = parentOf.get(id);
1026
+ let covered = false;
1027
+ for (let steps = 0; parent !== void 0 && steps < doc.nodes.length; steps += 1) {
1028
+ if (ids.has(parent)) {
1029
+ covered = true;
1030
+ break;
1031
+ }
1032
+ parent = parentOf.get(parent);
1033
+ }
1034
+ if (!covered) result.push(id);
1035
+ }
1036
+ return result;
1037
+ }
1038
+ function DiagramCanvas(props) {
1039
+ return /* @__PURE__ */ jsx6(ReactFlowProvider, { children: /* @__PURE__ */ jsx6(CanvasInner, { ...props }) });
1040
+ }
1041
+ function CanvasInner({
1042
+ value,
1043
+ defaultValue,
1044
+ onChange,
1045
+ readOnly = false,
1046
+ label,
1047
+ direction = "TB",
1048
+ dir: dirProp,
1049
+ messages: messagesOverride,
1050
+ height,
1051
+ toolbar = true,
1052
+ palette = NODE_KINDS,
1053
+ minimap = false,
1054
+ grid = true,
1055
+ snapToGrid = true,
1056
+ connectionRules,
1057
+ validateConnection,
1058
+ nodeRenderers,
1059
+ history,
1060
+ layoutOptions,
1061
+ onSelectionChange,
1062
+ onNodeActivate,
1063
+ outline = "hidden",
1064
+ edgesFocusable = false,
1065
+ virtualize = false,
1066
+ showAttribution = false,
1067
+ edgeKind,
1068
+ minZoom = 0.1,
1069
+ maxZoom = 2.5,
1070
+ wheelZoom = true,
1071
+ fitOnMount = true,
1072
+ ref,
1073
+ id: idProp,
1074
+ className,
1075
+ style,
1076
+ ...rest
1077
+ }) {
1078
+ const rf = useReactFlow();
1079
+ const messages = useMemo3(() => resolveMessages(messagesOverride), [messagesOverride]);
1080
+ const editable = !readOnly;
1081
+ const uid = useId3().replace(/[^a-zA-Z0-9_-]/g, "");
1082
+ const rootId = idProp ?? `kit-diagram-${uid}`;
1083
+ const instructionsId = `${rootId}-instructions`;
1084
+ const hostRef = useRef4(null);
1085
+ const stageRef = useRef4(null);
1086
+ const [pageRtl, setPageRtl] = useState3(false);
1087
+ useLayoutEffect(() => {
1088
+ if (dirProp !== void 0) return;
1089
+ const parent = hostRef.current?.parentElement;
1090
+ if (parent) setPageRtl(getComputedStyle(parent).direction === "rtl");
1091
+ }, [dirProp]);
1092
+ const rtl = dirProp === void 0 ? pageRtl : dirProp === "rtl";
1093
+ const controlled = value !== void 0;
1094
+ const own = useDiagramHistory(() => defaultValue ?? createDiagram());
1095
+ const doc = value ?? own.document;
1096
+ const docRef = useRef4(doc);
1097
+ docRef.current = doc;
1098
+ const latest = useRef4({ onChange, onSelectionChange, onNodeActivate, validateConnection });
1099
+ latest.current = { onChange, onSelectionChange, onNodeActivate, validateConnection };
1100
+ const lastEmitted = useRef4(own.document);
1101
+ const commit = useCallback2(
1102
+ (next, change) => {
1103
+ if (readOnly || next === docRef.current) return;
1104
+ docRef.current = next;
1105
+ if (!controlled) {
1106
+ lastEmitted.current = next;
1107
+ own.set(next, change.coalesceKey === void 0 ? void 0 : { coalesceKey: change.coalesceKey });
1108
+ }
1109
+ latest.current.onChange?.(next, change);
1110
+ },
1111
+ [readOnly, controlled, own]
1112
+ );
1113
+ useEffect3(() => {
1114
+ if (controlled || own.document === lastEmitted.current) return;
1115
+ lastEmitted.current = own.document;
1116
+ latest.current.onChange?.(own.document, { reason: "history" });
1117
+ }, [controlled, own.document]);
1118
+ const historySource = history ?? (controlled ? void 0 : own);
1119
+ const [viewCollapsed, setViewCollapsed] = useState3({});
1120
+ const viewDoc = useMemo3(() => {
1121
+ if (editable || Object.keys(viewCollapsed).length === 0) return doc;
1122
+ return {
1123
+ ...doc,
1124
+ nodes: doc.nodes.map((node) => isContainerKind(node.kind) && node.id in viewCollapsed ? { ...node, collapsed: viewCollapsed[node.id] === true } : node)
1125
+ };
1126
+ }, [doc, editable, viewCollapsed]);
1127
+ const model = useMemo3(
1128
+ () => buildFlowModel(viewDoc, { direction, rtl, editable, messages, edgesFocusable }),
1129
+ [viewDoc, direction, rtl, editable, messages, edgesFocusable]
1130
+ );
1131
+ const visibleIds = useMemo3(() => model.order.filter((id) => !model.hidden.has(id)), [model]);
1132
+ const [selection, setSelection] = useState3(NO_SELECTION);
1133
+ const selectedNodes = useMemo3(
1134
+ () => new Set([...selection.nodes].filter((id) => model.index.nodesById.has(id) && !model.hidden.has(id))),
1135
+ [selection.nodes, model]
1136
+ );
1137
+ const selectedEdges = useMemo3(() => {
1138
+ const live = new Set(viewDoc.edges.filter((edge) => !model.hidden.has(edge.source) && !model.hidden.has(edge.target)).map((edge) => edge.id));
1139
+ return new Set([...selection.edges].filter((id) => live.has(id)));
1140
+ }, [selection.edges, viewDoc.edges, model]);
1141
+ const [overlay, setOverlay] = useState3(NO_OVERLAY);
1142
+ const flowNodes = useMemo3(
1143
+ () => model.nodes.map((node) => {
1144
+ const selected = selectedNodes.has(node.id);
1145
+ const position = overlay.positions[node.id];
1146
+ const size = overlay.sizes[node.id];
1147
+ if (!selected && position === void 0 && size === void 0) return node;
1148
+ return {
1149
+ ...node,
1150
+ selected,
1151
+ ...position === void 0 ? {} : { position },
1152
+ ...size === void 0 ? {} : { width: size.width, height: size.height }
1153
+ };
1154
+ }),
1155
+ [model.nodes, selectedNodes, overlay]
1156
+ );
1157
+ const flowEdges = useMemo3(
1158
+ () => model.edges.map((edge) => selectedEdges.has(edge.id) ? { ...edge, selected: true } : edge),
1159
+ [model.edges, selectedEdges]
1160
+ );
1161
+ const labelOfNode = useCallback2(
1162
+ (id) => {
1163
+ const node = model.index.nodesById.get(id);
1164
+ return node ? displayLabel(node, messages) : id;
1165
+ },
1166
+ [model, messages]
1167
+ );
1168
+ const [announcement, setAnnouncement] = useState3({ text: "", n: 0 });
1169
+ const announce = useCallback2((text) => setAnnouncement((previous) => ({ text, n: previous.n + 1 })), []);
1170
+ const [notice, setNotice] = useState3(void 0);
1171
+ const noticeTimer = useRef4(void 0);
1172
+ const showNotice = useCallback2((text) => {
1173
+ setNotice((previous) => ({ text, n: (previous?.n ?? 0) + 1 }));
1174
+ clearTimeout(noticeTimer.current);
1175
+ noticeTimer.current = setTimeout(() => setNotice(void 0), NOTICE_MS);
1176
+ }, []);
1177
+ useEffect3(() => () => clearTimeout(noticeTimer.current), []);
1178
+ const [showMiniMap, setShowMiniMap] = useState3(minimap);
1179
+ const [showGrid, setShowGrid] = useState3(grid);
1180
+ const [paletteOpen, setPaletteOpen] = useState3(false);
1181
+ const snap = snapToGrid === false ? 0 : snapToGrid === true ? GRID_SIZE : snapToGrid;
1182
+ const findElement = useCallback2((selector, id) => {
1183
+ const root = hostRef.current;
1184
+ if (!root) return void 0;
1185
+ return Array.from(root.querySelectorAll(selector)).find((element) => element.dataset.id === id);
1186
+ }, []);
1187
+ const focusNodeElement = useCallback2(
1188
+ (id) => {
1189
+ const element = findElement(".react-flow__node", id);
1190
+ if (!element) return false;
1191
+ element.focus();
1192
+ return true;
1193
+ },
1194
+ [findElement]
1195
+ );
1196
+ const focusWatch = useRef4(void 0);
1197
+ const stopFocusWatch = useCallback2(() => {
1198
+ const watch = focusWatch.current;
1199
+ if (!watch) return;
1200
+ watch.observer.disconnect();
1201
+ clearTimeout(watch.timer);
1202
+ focusWatch.current = void 0;
1203
+ }, []);
1204
+ const requestFocus = useCallback2(
1205
+ (id) => {
1206
+ stopFocusWatch();
1207
+ if (id === void 0) {
1208
+ hostRef.current?.focus();
1209
+ return;
1210
+ }
1211
+ if (focusNodeElement(id)) return;
1212
+ const host = hostRef.current;
1213
+ if (!host || typeof MutationObserver === "undefined") return;
1214
+ const observer = new MutationObserver(() => {
1215
+ if (focusNodeElement(id)) stopFocusWatch();
1216
+ });
1217
+ observer.observe(host, { childList: true, subtree: true });
1218
+ focusWatch.current = { observer, timer: setTimeout(stopFocusWatch, FOCUS_WATCH_MS) };
1219
+ },
1220
+ [focusNodeElement, stopFocusWatch]
1221
+ );
1222
+ useEffect3(() => stopFocusWatch, [stopFocusWatch]);
1223
+ const afterFrame = useCallback2((run) => {
1224
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
1225
+ else setTimeout(run, 0);
1226
+ }, []);
1227
+ const duration = () => prefersReducedMotion() ? 0 : ANIMATION_MS;
1228
+ const fit = useCallback2(() => void rf.fitView({ padding: FIT_OPTIONS.padding, duration: duration() }), [rf]);
1229
+ const zoomIn = useCallback2(() => void rf.zoomIn({ duration: duration() }), [rf]);
1230
+ const zoomOut = useCallback2(() => void rf.zoomOut({ duration: duration() }), [rf]);
1231
+ const select = useCallback2((nodeIds, edgeIds = []) => {
1232
+ setSelection({ nodes: new Set(nodeIds), edges: new Set(edgeIds) });
1233
+ }, []);
1234
+ const clearSelection = useCallback2(() => setSelection(NO_SELECTION), []);
1235
+ const selectionKey = `${[...selectedNodes].join("|")}#${[...selectedEdges].join("|")}`;
1236
+ const previousSelection = useRef4("#");
1237
+ const silentKey = useRef4(void 0);
1238
+ const selectQuietly = useCallback2((nodeIds, edgeIds = []) => {
1239
+ const key = `${[...new Set(nodeIds)].join("|")}#${[...new Set(edgeIds)].join("|")}`;
1240
+ if (key !== previousSelection.current) silentKey.current = key;
1241
+ select(nodeIds, edgeIds);
1242
+ }, [select]);
1243
+ const clearSelectionQuietly = useCallback2(() => {
1244
+ if (previousSelection.current !== "#") silentKey.current = "#";
1245
+ clearSelection();
1246
+ }, [clearSelection]);
1247
+ useEffect3(() => {
1248
+ if (selectionKey === previousSelection.current) return;
1249
+ const hadAny = previousSelection.current !== "#";
1250
+ previousSelection.current = selectionKey;
1251
+ latest.current.onSelectionChange?.({ nodeIds: [...selectedNodes], edgeIds: [...selectedEdges] });
1252
+ const quiet = silentKey.current === selectionKey;
1253
+ silentKey.current = void 0;
1254
+ if (quiet) return;
1255
+ const count = selectedNodes.size + selectedEdges.size;
1256
+ if (count === 1) {
1257
+ const [nodeId] = [...selectedNodes];
1258
+ const [edgeId] = [...selectedEdges];
1259
+ let name = "";
1260
+ if (nodeId !== void 0) name = labelOfNode(nodeId);
1261
+ else if (edgeId !== void 0) {
1262
+ const edge = viewDoc.edges.find((candidate) => candidate.id === edgeId);
1263
+ name = edge ? formatMessage(messages.edge.label, { source: labelOfNode(edge.source), target: labelOfNode(edge.target) }) : edgeId;
1264
+ }
1265
+ announce(formatMessage(messages.announce.selected, { label: name }));
1266
+ } else if (count > 1) {
1267
+ announce(formatMessage(messages.announce.selectedMany, { count }));
1268
+ } else if (hadAny) {
1269
+ announce(formatMessage(messages.announce.selectionCleared));
1270
+ }
1271
+ }, [selectionKey]);
1272
+ const [editing, setEditing] = useState3(void 0);
1273
+ const restoreFocus = useRef4(void 0);
1274
+ const startEditing = useCallback2(
1275
+ (target) => {
1276
+ if (!editable) return;
1277
+ if (target.kind === "node" && docRef.current.nodes.find((node) => node.id === target.id)?.locked === true) {
1278
+ announce(formatMessage(messages.announce.locked, { label: labelOfNode(target.id) }));
1279
+ return;
1280
+ }
1281
+ setEditing(target);
1282
+ },
1283
+ [editable, announce, messages, labelOfNode]
1284
+ );
1285
+ const cancelEditing = useCallback2(() => {
1286
+ restoreFocus.current = editing;
1287
+ setEditing(void 0);
1288
+ }, [editing]);
1289
+ const commitEditing = useCallback2(
1290
+ (text) => {
1291
+ const target = editing;
1292
+ if (!target) return;
1293
+ restoreFocus.current = target;
1294
+ setEditing(void 0);
1295
+ const current = docRef.current;
1296
+ if (target.kind === "node") {
1297
+ const node = current.nodes.find((candidate) => candidate.id === target.id);
1298
+ if (!node || node.label === text) return;
1299
+ commit(updateNode(current, target.id, { label: text }), { reason: "rename", nodeIds: [target.id] });
1300
+ announce(formatMessage(messages.announce.renamed, { label: text !== "" ? text : formatMessage(messages.kinds[node.kind]) }));
1301
+ } else {
1302
+ const edge = current.edges.find((candidate) => candidate.id === target.id);
1303
+ if (!edge || (edge.label ?? "") === text) return;
1304
+ commit(updateEdge(current, target.id, { label: text === "" ? void 0 : text }), { reason: "rename-edge", edgeIds: [target.id] });
1305
+ announce(formatMessage(messages.announce.renamed, { label: text }));
1306
+ }
1307
+ },
1308
+ [editing, commit, announce, messages]
1309
+ );
1310
+ useEffect3(() => {
1311
+ if (editing !== void 0 || restoreFocus.current === void 0) return;
1312
+ const target = restoreFocus.current;
1313
+ restoreFocus.current = void 0;
1314
+ const active = typeof document === "undefined" ? null : document.activeElement;
1315
+ if (active !== null && active !== document.body) return;
1316
+ if (target.kind === "node") focusNodeElement(target.id);
1317
+ else (findElement(".react-flow__edge", target.id) ?? hostRef.current)?.focus();
1318
+ }, [editing, focusNodeElement, findElement]);
1319
+ const [connectingFrom, setConnectingFrom] = useState3(void 0);
1320
+ const checkProposed = useCallback2(
1321
+ (connection) => {
1322
+ const current = docRef.current;
1323
+ const result = checkConnection(current, connection, connectionRules, model.index);
1324
+ if (!result.ok) return { ok: false, text: formatMessage(messages.connection[result.code]) };
1325
+ const custom = latest.current.validateConnection?.(connection, current);
1326
+ if (custom === void 0 || custom === true) return { ok: true };
1327
+ return { ok: false, text: custom === false ? formatMessage(messages.connection.custom) : custom };
1328
+ },
1329
+ [connectionRules, model.index, messages]
1330
+ );
1331
+ const toConnection = (connection) => ({
1332
+ source: connection.source,
1333
+ target: connection.target,
1334
+ ...connection.sourceHandle ? { sourcePort: connection.sourceHandle } : {},
1335
+ ...connection.targetHandle ? { targetPort: connection.targetHandle } : {}
1336
+ });
1337
+ const tryConnect = useCallback2(
1338
+ (proposed) => {
1339
+ const check = checkProposed(proposed);
1340
+ if (!check.ok) {
1341
+ showNotice(check.text);
1342
+ return false;
1343
+ }
1344
+ const current = docRef.current;
1345
+ const id = nextEdgeId(current);
1346
+ commit(connect(current, { ...proposed, id }, { validate: false }), { reason: "connect", edgeIds: [id], nodeIds: [proposed.source, proposed.target] });
1347
+ announce(formatMessage(messages.announce.connected, { source: labelOfNode(proposed.source), target: labelOfNode(proposed.target) }));
1348
+ return true;
1349
+ },
1350
+ [checkProposed, commit, showNotice, announce, messages, labelOfNode]
1351
+ );
1352
+ const addNodeAt = useCallback2(
1353
+ (kind, center, init = {}) => {
1354
+ if (!editable) return void 0;
1355
+ const current = docRef.current;
1356
+ const index = model.index;
1357
+ let parentId = init.parentId;
1358
+ if (parentId === void 0) {
1359
+ const bounds = getAbsoluteBounds(current, index);
1360
+ let best;
1361
+ for (const node of current.nodes) {
1362
+ if (!isContainerKind(node.kind) || node.collapsed === true || model.hidden.has(node.id)) continue;
1363
+ const box = bounds.get(node.id);
1364
+ if (!box || center.x < box.x || center.y < box.y || center.x > box.x + box.width || center.y > box.y + box.height) continue;
1365
+ const area = box.width * box.height;
1366
+ if (!best || area < best.area) best = { id: node.id, area };
1367
+ }
1368
+ parentId = best?.id;
1369
+ }
1370
+ const size = getNodeSize({ id: "", kind, label: "", position: { x: 0, y: 0 } });
1371
+ let at = { x: center.x - size.width / 2, y: center.y - size.height / 2 };
1372
+ if (parentId !== void 0) {
1373
+ const origin = getAbsoluteBounds(current, index).get(parentId);
1374
+ if (origin) at = { x: at.x - origin.x, y: at.y - origin.y };
1375
+ }
1376
+ if (snap > 0) at = snapPoint(at, snap);
1377
+ const siblings = current.nodes.filter((node) => node.parentId === parentId);
1378
+ while (siblings.some((node) => node.position.x === at.x && node.position.y === at.y)) at = { x: at.x + PASTE_STEP, y: at.y + PASTE_STEP };
1379
+ const id = init.id ?? nextNodeId(current, kind);
1380
+ const next = addNode(current, { ...init, kind, id, position: init.position ?? at, ...parentId === void 0 ? {} : { parentId } });
1381
+ commit(next, { reason: "add-node", nodeIds: [id] });
1382
+ selectQuietly([id]);
1383
+ announce(formatMessage(messages.announce.nodeAdded, { kind: formatMessage(messages.kinds[kind]), label: init.label ?? formatMessage(messages.kinds[kind]) }));
1384
+ return id;
1385
+ },
1386
+ [editable, model, snap, commit, selectQuietly, announce, messages]
1387
+ );
1388
+ const viewCenter = useCallback2(() => {
1389
+ const box = stageRef.current?.getBoundingClientRect();
1390
+ const point = rf.screenToFlowPosition({ x: (box?.left ?? 0) + (box?.width ?? 0) / 2, y: (box?.top ?? 0) + (box?.height ?? 0) / 2 });
1391
+ return Number.isFinite(point.x) && Number.isFinite(point.y) ? point : { x: 0, y: 0 };
1392
+ }, [rf]);
1393
+ const addFromPalette = useCallback2(
1394
+ (kind) => {
1395
+ const id = addNodeAt(kind, viewCenter());
1396
+ setPaletteOpen(false);
1397
+ if (id !== void 0) startEditing({ kind: "node", id });
1398
+ },
1399
+ [addNodeAt, viewCenter, startEditing]
1400
+ );
1401
+ const editableSelection = useCallback2(() => {
1402
+ const current = docRef.current;
1403
+ return topLevel(selectedNodes, current).filter((id) => current.nodes.find((node) => node.id === id)?.locked !== true);
1404
+ }, [selectedNodes]);
1405
+ const deleteTargets = useCallback2(
1406
+ (nodeIds, edgeIds) => {
1407
+ if (!editable) return;
1408
+ const current = docRef.current;
1409
+ const removable = nodeIds.filter((id) => current.nodes.find((node) => node.id === id)?.locked !== true);
1410
+ if (removable.length === 0 && edgeIds.length === 0) {
1411
+ const lockedId = nodeIds[0];
1412
+ if (lockedId !== void 0) announce(formatMessage(messages.announce.locked, { label: labelOfNode(lockedId) }));
1413
+ return;
1414
+ }
1415
+ const gone = new Set(removable);
1416
+ const first = removable[0];
1417
+ let successor;
1418
+ if (first !== void 0) {
1419
+ const at = visibleIds.indexOf(first);
1420
+ successor = visibleIds.slice(at + 1).find((id) => !gone.has(id)) ?? visibleIds.slice(0, Math.max(at, 0)).reverse().find((id) => !gone.has(id));
1421
+ }
1422
+ let next = removeEdges(current, edgeIds);
1423
+ next = removeNodes(next, removable, { children: "release" });
1424
+ commit(next, { reason: "delete", nodeIds: removable, edgeIds: [...edgeIds] });
1425
+ clearSelectionQuietly();
1426
+ const count = removable.length + edgeIds.length;
1427
+ announce(
1428
+ removable.length === 1 && edgeIds.length === 0 && first !== void 0 ? formatMessage(messages.announce.nodeRemoved, { label: labelOfNode(first) }) : formatMessage(messages.announce.itemsRemoved, { count })
1429
+ );
1430
+ requestFocus(successor);
1431
+ },
1432
+ [editable, commit, clearSelectionQuietly, announce, messages, labelOfNode, visibleIds, requestFocus]
1433
+ );
1434
+ const deleteSelection = useCallback2(() => deleteTargets([...selectedNodes], [...selectedEdges]), [deleteTargets, selectedNodes, selectedEdges]);
1435
+ const duplicate = useCallback2(() => {
1436
+ if (!editable) return;
1437
+ const ids = topLevel(selectedNodes, docRef.current);
1438
+ if (ids.length === 0) return;
1439
+ const result = duplicateNodes(docRef.current, ids, { offset: { x: PASTE_STEP, y: PASTE_STEP } });
1440
+ commit(result.document, { reason: "duplicate", nodeIds: result.nodeIds, edgeIds: result.edgeIds });
1441
+ selectQuietly(result.rootIds);
1442
+ announce(formatMessage(messages.announce.duplicated, { count: result.nodeIds.length }));
1443
+ }, [editable, selectedNodes, commit, selectQuietly, announce, messages]);
1444
+ const pasteCount = useRef4(0);
1445
+ const copy = useCallback2(() => {
1446
+ const ids = topLevel(selectedNodes, docRef.current);
1447
+ if (ids.length === 0) return;
1448
+ clipboard = extractFragment(docRef.current, ids);
1449
+ pasteCount.current = 0;
1450
+ announce(formatMessage(messages.announce.copied, { count: clipboard.nodes.length }));
1451
+ }, [selectedNodes, announce, messages]);
1452
+ const paste = useCallback2(() => {
1453
+ if (!editable || clipboard === void 0) return;
1454
+ pasteCount.current += 1;
1455
+ const step = PASTE_STEP * pasteCount.current;
1456
+ const result = pasteFragment(docRef.current, clipboard, { offset: { x: step, y: step } });
1457
+ commit(result.document, { reason: "paste", nodeIds: result.nodeIds, edgeIds: result.edgeIds });
1458
+ selectQuietly(result.rootIds);
1459
+ announce(formatMessage(messages.announce.pasted, { count: result.nodeIds.length }));
1460
+ }, [editable, commit, selectQuietly, announce, messages]);
1461
+ const cut = useCallback2(() => {
1462
+ if (!editable) return;
1463
+ copy();
1464
+ deleteSelection();
1465
+ }, [editable, copy, deleteSelection]);
1466
+ const selectAll = useCallback2(() => {
1467
+ selectQuietly(visibleIds);
1468
+ announce(formatMessage(messages.announce.selectedMany, { count: visibleIds.length }));
1469
+ }, [selectQuietly, visibleIds, announce, messages]);
1470
+ const canGroup = useMemo3(() => {
1471
+ if (!editable || selectedNodes.size === 0) return false;
1472
+ const nodes = [...selectedNodes].map((id) => model.index.nodesById.get(id));
1473
+ if (nodes.some((node) => node === void 0 || node.locked === true)) return false;
1474
+ const members = topLevel(selectedNodes, viewDoc);
1475
+ return new Set(members.map((id) => model.index.nodesById.get(id)?.parentId)).size === 1;
1476
+ }, [editable, selectedNodes, model, viewDoc]);
1477
+ const ungroupable = useMemo3(
1478
+ () => editable ? [...selectedNodes].filter((id) => {
1479
+ const node = model.index.nodesById.get(id);
1480
+ return node !== void 0 && isContainerKind(node.kind) && node.locked !== true;
1481
+ }) : [],
1482
+ [editable, selectedNodes, model]
1483
+ );
1484
+ const group = useCallback2(() => {
1485
+ if (!canGroup) return;
1486
+ const current = docRef.current;
1487
+ const members = topLevel(selectedNodes, current);
1488
+ const id = nextNodeId(current, "group");
1489
+ try {
1490
+ commit(groupNodes(current, members, { id }), { reason: "group", nodeIds: [id, ...members] });
1491
+ } catch (error) {
1492
+ if (error instanceof DiagramMutationError) return;
1493
+ throw error;
1494
+ }
1495
+ selectQuietly([id]);
1496
+ announce(formatMessage(messages.announce.grouped, { label: formatMessage(messages.kinds.group) }));
1497
+ startEditing({ kind: "node", id });
1498
+ }, [canGroup, selectedNodes, commit, selectQuietly, announce, messages, startEditing]);
1499
+ const ungroup = useCallback2(() => {
1500
+ if (ungroupable.length === 0) return;
1501
+ let next = docRef.current;
1502
+ const released = [];
1503
+ for (const id of ungroupable) {
1504
+ for (const node of next.nodes) if (node.parentId === id) released.push(node.id);
1505
+ next = ungroupNode(next, id);
1506
+ }
1507
+ commit(next, { reason: "ungroup", nodeIds: [...ungroupable, ...released] });
1508
+ selectQuietly(released);
1509
+ announce(formatMessage(messages.announce.ungrouped, { label: ungroupable.map(labelOfNode).join(", ") }));
1510
+ }, [ungroupable, commit, selectQuietly, announce, messages, labelOfNode]);
1511
+ const applyAutoLayout = useCallback2(
1512
+ (options) => {
1513
+ if (!editable) return;
1514
+ const next = layoutedDiagram(docRef.current, { direction, rtl, ...layoutOptions, ...options });
1515
+ commit(next, { reason: "layout" });
1516
+ announce(formatMessage(messages.announce.layoutApplied));
1517
+ afterFrame(fit);
1518
+ },
1519
+ [editable, direction, rtl, layoutOptions, commit, announce, messages, afterFrame, fit]
1520
+ );
1521
+ const align = useCallback2(
1522
+ (alignment) => {
1523
+ if (!editable) return;
1524
+ const ids = editableSelection();
1525
+ commit(alignNodes(docRef.current, ids, alignment), { reason: "align", nodeIds: ids });
1526
+ },
1527
+ [editable, editableSelection, commit]
1528
+ );
1529
+ const distribute = useCallback2(
1530
+ (axis) => {
1531
+ if (!editable) return;
1532
+ const ids = editableSelection();
1533
+ commit(distributeNodes(docRef.current, ids, axis), { reason: "distribute", nodeIds: ids });
1534
+ },
1535
+ [editable, editableSelection, commit]
1536
+ );
1537
+ const nudge = useCallback2(
1538
+ (ids, dx, dy) => {
1539
+ if (!editable) return;
1540
+ const current = docRef.current;
1541
+ const movable = topLevel(new Set(ids), current).filter((id) => current.nodes.find((node) => node.id === id)?.locked !== true);
1542
+ if (movable.length === 0) return;
1543
+ commit(translateNodes(current, movable, dx, dy), { reason: "move", nodeIds: movable, coalesceKey: `nudge:${movable.join(",")}` });
1544
+ const only = movable.length === 1 ? movable[0] : void 0;
1545
+ if (only !== void 0) {
1546
+ const moved = docRef.current.nodes.find((node) => node.id === only);
1547
+ if (moved) announce(formatMessage(messages.announce.moved, { label: labelOfNode(only), x: Math.round(moved.position.x), y: Math.round(moved.position.y) }));
1548
+ }
1549
+ },
1550
+ [editable, commit, announce, messages, labelOfNode]
1551
+ );
1552
+ const undo = useCallback2(() => {
1553
+ if (!historySource?.canUndo) {
1554
+ announce(formatMessage(messages.announce.nothingToUndo));
1555
+ return;
1556
+ }
1557
+ historySource.undo();
1558
+ announce(formatMessage(messages.announce.undone));
1559
+ }, [historySource, announce, messages]);
1560
+ const redo = useCallback2(() => {
1561
+ if (!historySource?.canRedo) {
1562
+ announce(formatMessage(messages.announce.nothingToRedo));
1563
+ return;
1564
+ }
1565
+ historySource.redo();
1566
+ announce(formatMessage(messages.announce.redone));
1567
+ }, [historySource, announce, messages]);
1568
+ const toggleCollapsed = useCallback2(
1569
+ (id) => {
1570
+ const node = model.index.nodesById.get(id);
1571
+ if (!node) return;
1572
+ const collapsed = node.collapsed !== true;
1573
+ if (editable) commit(updateNode(docRef.current, id, { collapsed }), { reason: "collapse", nodeIds: [id] });
1574
+ else setViewCollapsed((previous) => ({ ...previous, [id]: collapsed }));
1575
+ },
1576
+ [model, editable, commit]
1577
+ );
1578
+ const commitResize = useCallback2(
1579
+ (id, box) => {
1580
+ setOverlay((previous) => {
1581
+ if (!(id in previous.positions) && !(id in previous.sizes)) return previous;
1582
+ const positions = { ...previous.positions };
1583
+ const sizes = { ...previous.sizes };
1584
+ delete positions[id];
1585
+ delete sizes[id];
1586
+ return { positions, sizes };
1587
+ });
1588
+ if (!editable) return;
1589
+ const size = { width: Math.round(box.width), height: Math.round(box.height) };
1590
+ commit(resizeNode(docRef.current, id, size, { x: round(box.x), y: round(box.y) }), { reason: "resize", nodeIds: [id] });
1591
+ announce(formatMessage(messages.announce.resized, { label: labelOfNode(id), width: size.width, height: size.height }));
1592
+ },
1593
+ [editable, commit, announce, messages, labelOfNode]
1594
+ );
1595
+ const focusNode = useCallback2(
1596
+ (id) => {
1597
+ const node = model.index.nodesById.get(id);
1598
+ if (!node) return false;
1599
+ const closed = getAncestors(model.index, id).filter((ancestor) => model.index.nodesById.get(ancestor)?.collapsed === true);
1600
+ if (closed.length > 0) {
1601
+ if (editable) {
1602
+ let next = docRef.current;
1603
+ for (const ancestor of closed) next = updateNode(next, ancestor, { collapsed: false });
1604
+ commit(next, { reason: "collapse", nodeIds: closed });
1605
+ } else {
1606
+ setViewCollapsed((previous) => ({ ...previous, ...Object.fromEntries(closed.map((ancestor) => [ancestor, false])) }));
1607
+ }
1608
+ }
1609
+ requestFocus(id);
1610
+ return true;
1611
+ },
1612
+ [model, editable, commit, requestFocus]
1613
+ );
1614
+ const onNodesChange = useCallback2((changes) => {
1615
+ const positions = {};
1616
+ const sizes = {};
1617
+ const toggled = /* @__PURE__ */ new Map();
1618
+ for (const change of changes) {
1619
+ if (change.type === "position" && change.position && change.dragging !== false) positions[change.id] = change.position;
1620
+ else if (change.type === "dimensions" && change.setAttributes && change.dimensions) sizes[change.id] = change.dimensions;
1621
+ else if (change.type === "select") toggled.set(change.id, change.selected);
1622
+ }
1623
+ if (Object.keys(positions).length > 0 || Object.keys(sizes).length > 0) {
1624
+ setOverlay((previous) => ({ positions: { ...previous.positions, ...positions }, sizes: { ...previous.sizes, ...sizes } }));
1625
+ }
1626
+ if (toggled.size > 0) {
1627
+ setSelection((previous) => {
1628
+ const nodes = new Set(previous.nodes);
1629
+ for (const [id, selected] of toggled) {
1630
+ if (selected) nodes.add(id);
1631
+ else nodes.delete(id);
1632
+ }
1633
+ return { nodes, edges: previous.edges };
1634
+ });
1635
+ }
1636
+ }, []);
1637
+ const onEdgesChange = useCallback2((changes) => {
1638
+ const toggled = /* @__PURE__ */ new Map();
1639
+ for (const change of changes) if (change.type === "select") toggled.set(change.id, change.selected);
1640
+ if (toggled.size === 0) return;
1641
+ setSelection((previous) => {
1642
+ const edges = new Set(previous.edges);
1643
+ for (const [id, selected] of toggled) {
1644
+ if (selected) edges.add(id);
1645
+ else edges.delete(id);
1646
+ }
1647
+ return { nodes: previous.nodes, edges };
1648
+ });
1649
+ }, []);
1650
+ const onNodeDragStop = useCallback2(
1651
+ (_event, _node, nodes) => {
1652
+ const positions = {};
1653
+ for (const node of nodes) positions[node.id] = { x: round(node.position.x), y: round(node.position.y) };
1654
+ setOverlay((previous) => {
1655
+ const next = { ...previous.positions };
1656
+ for (const id of Object.keys(positions)) delete next[id];
1657
+ return { positions: next, sizes: previous.sizes };
1658
+ });
1659
+ if (!editable) return;
1660
+ const ids = Object.keys(positions);
1661
+ commit(moveNodes(docRef.current, positions), { reason: "move", nodeIds: ids });
1662
+ const only = ids.length === 1 ? ids[0] : void 0;
1663
+ const at = only === void 0 ? void 0 : positions[only];
1664
+ if (only !== void 0 && at !== void 0) announce(formatMessage(messages.announce.moved, { label: labelOfNode(only), x: Math.round(at.x), y: Math.round(at.y) }));
1665
+ },
1666
+ [editable, commit, announce, messages, labelOfNode]
1667
+ );
1668
+ const onConnect = useCallback2((connection) => void tryConnect(toConnection(connection)), [tryConnect]);
1669
+ const isValidConnection = useCallback2(
1670
+ (connection) => checkProposed(toConnection(connection)).ok,
1671
+ [checkProposed]
1672
+ );
1673
+ const onConnectEnd = useCallback2(
1674
+ (_event, state) => {
1675
+ if (state.isValid !== false || !state.toNode || !state.fromNode || !state.fromHandle || !state.toHandle) return;
1676
+ const from = state.fromHandle.type === "source" ? state : void 0;
1677
+ const source = from ? state.fromNode.id : state.toNode.id;
1678
+ const target = from ? state.toNode.id : state.fromNode.id;
1679
+ const sourceHandle = from ? state.fromHandle.id : state.toHandle.id;
1680
+ const targetHandle = from ? state.toHandle.id : state.fromHandle.id;
1681
+ const check = checkProposed(toConnection({ source, target, sourceHandle: sourceHandle ?? null, targetHandle: targetHandle ?? null }));
1682
+ if (!check.ok) showNotice(check.text);
1683
+ },
1684
+ [checkProposed, showNotice]
1685
+ );
1686
+ const activate = useCallback2((id) => {
1687
+ const node = docRef.current.nodes.find((candidate) => candidate.id === id);
1688
+ if (node) latest.current.onNodeActivate?.(node);
1689
+ }, []);
1690
+ const onKeyDown = (event) => {
1691
+ if (event.defaultPrevented) return;
1692
+ const target = event.target;
1693
+ if (isTextEntry(target)) return;
1694
+ const key = event.key;
1695
+ if ((key === "Enter" || key === " ") && target.closest("button, a[href]")) return;
1696
+ const nodeId = target.closest(".react-flow__node")?.dataset.id;
1697
+ const edgeId = nodeId === void 0 ? target.closest(".react-flow__edge")?.dataset.id : void 0;
1698
+ const modifier = event.ctrlKey || event.metaKey;
1699
+ if (modifier) {
1700
+ switch (key.toLowerCase()) {
1701
+ case "z":
1702
+ if (!historySource) return;
1703
+ event.preventDefault();
1704
+ if (event.shiftKey) redo();
1705
+ else undo();
1706
+ return;
1707
+ case "y":
1708
+ if (!historySource) return;
1709
+ event.preventDefault();
1710
+ redo();
1711
+ return;
1712
+ case "c":
1713
+ if (selectedNodes.size === 0) return;
1714
+ event.preventDefault();
1715
+ copy();
1716
+ return;
1717
+ case "x":
1718
+ if (!editable || selectedNodes.size === 0) return;
1719
+ event.preventDefault();
1720
+ cut();
1721
+ return;
1722
+ case "v":
1723
+ if (!editable || clipboard === void 0) return;
1724
+ event.preventDefault();
1725
+ paste();
1726
+ return;
1727
+ case "d":
1728
+ if (!editable || selectedNodes.size === 0) return;
1729
+ event.preventDefault();
1730
+ duplicate();
1731
+ return;
1732
+ case "a":
1733
+ event.preventDefault();
1734
+ selectAll();
1735
+ return;
1736
+ case "enter":
1737
+ if (nodeId === void 0) return;
1738
+ event.preventDefault();
1739
+ activate(nodeId);
1740
+ return;
1741
+ default:
1742
+ return;
1743
+ }
1744
+ }
1745
+ if (event.altKey) return;
1746
+ const arrow = ARROWS[key];
1747
+ if (arrow !== void 0) {
1748
+ if (event.shiftKey) {
1749
+ if (!editable) return;
1750
+ const ids = selectedNodes.size > 0 ? [...selectedNodes] : nodeId === void 0 ? [] : [nodeId];
1751
+ if (ids.length === 0) return;
1752
+ event.preventDefault();
1753
+ const step = snap > 0 ? snap : NUDGE_FALLBACK;
1754
+ nudge(ids, arrow === "left" ? -step : arrow === "right" ? step : 0, arrow === "up" ? -step : arrow === "down" ? step : 0);
1755
+ return;
1756
+ }
1757
+ if (nodeId === void 0) {
1758
+ const firstId = visibleIds[0];
1759
+ if (firstId === void 0 || edgeId !== void 0) return;
1760
+ event.preventDefault();
1761
+ focusNodeElement(firstId);
1762
+ return;
1763
+ }
1764
+ event.preventDefault();
1765
+ const next = findDirectionalNeighbor(viewDoc, nodeId, arrow, { candidates: visibleIds });
1766
+ if (next !== void 0) focusNodeElement(next);
1767
+ return;
1768
+ }
1769
+ switch (key) {
1770
+ case " ": {
1771
+ if (nodeId === void 0 && edgeId === void 0) return;
1772
+ event.preventDefault();
1773
+ const additive = event.shiftKey;
1774
+ if (nodeId !== void 0) {
1775
+ if (additive) {
1776
+ const next = new Set(selectedNodes);
1777
+ if (next.has(nodeId)) next.delete(nodeId);
1778
+ else next.add(nodeId);
1779
+ select([...next], [...selectedEdges]);
1780
+ } else if (selectedNodes.size === 1 && selectedNodes.has(nodeId) && selectedEdges.size === 0) clearSelection();
1781
+ else select([nodeId]);
1782
+ } else if (edgeId !== void 0) {
1783
+ if (selectedEdges.size === 1 && selectedEdges.has(edgeId) && selectedNodes.size === 0) clearSelection();
1784
+ else select([], [edgeId]);
1785
+ }
1786
+ return;
1787
+ }
1788
+ case "Enter": {
1789
+ if (connectingFrom !== void 0 && nodeId !== void 0) {
1790
+ event.preventDefault();
1791
+ tryConnect({ source: connectingFrom, target: nodeId });
1792
+ setConnectingFrom(void 0);
1793
+ return;
1794
+ }
1795
+ event.preventDefault();
1796
+ if (!editable) {
1797
+ if (nodeId !== void 0) activate(nodeId);
1798
+ } else if (nodeId !== void 0) startEditing({ kind: "node", id: nodeId });
1799
+ else if (edgeId !== void 0) startEditing({ kind: "edge", id: edgeId });
1800
+ return;
1801
+ }
1802
+ case "F2":
1803
+ if (!editable) return;
1804
+ event.preventDefault();
1805
+ if (nodeId !== void 0) startEditing({ kind: "node", id: nodeId });
1806
+ else if (edgeId !== void 0) startEditing({ kind: "edge", id: edgeId });
1807
+ return;
1808
+ case "Delete":
1809
+ case "Backspace": {
1810
+ if (!editable) return;
1811
+ const nodes = selectedNodes.size > 0 || selectedEdges.size > 0 ? [...selectedNodes] : nodeId === void 0 ? [] : [nodeId];
1812
+ const edges = selectedNodes.size > 0 || selectedEdges.size > 0 ? [...selectedEdges] : edgeId === void 0 ? [] : [edgeId];
1813
+ if (nodes.length === 0 && edges.length === 0) return;
1814
+ event.preventDefault();
1815
+ deleteTargets(nodes, edges);
1816
+ return;
1817
+ }
1818
+ case "Escape":
1819
+ if (connectingFrom !== void 0) {
1820
+ event.preventDefault();
1821
+ setConnectingFrom(void 0);
1822
+ announce(formatMessage(messages.announce.connectCancelled));
1823
+ } else if (selectedNodes.size > 0 || selectedEdges.size > 0) {
1824
+ event.preventDefault();
1825
+ clearSelection();
1826
+ }
1827
+ return;
1828
+ case "c":
1829
+ case "C": {
1830
+ if (!editable || nodeId === void 0) return;
1831
+ event.preventDefault();
1832
+ if (connectingFrom === void 0) {
1833
+ setConnectingFrom(nodeId);
1834
+ announce(formatMessage(messages.announce.connectStarted, { label: labelOfNode(nodeId) }));
1835
+ } else {
1836
+ tryConnect({ source: connectingFrom, target: nodeId });
1837
+ setConnectingFrom(void 0);
1838
+ }
1839
+ return;
1840
+ }
1841
+ case "+":
1842
+ case "=":
1843
+ event.preventDefault();
1844
+ zoomIn();
1845
+ return;
1846
+ case "-":
1847
+ case "_":
1848
+ event.preventDefault();
1849
+ zoomOut();
1850
+ return;
1851
+ case "0":
1852
+ event.preventDefault();
1853
+ fit();
1854
+ return;
1855
+ default:
1856
+ return;
1857
+ }
1858
+ };
1859
+ const onDragOver = (event) => {
1860
+ if (!editable || !Array.from(event.dataTransfer.types).includes(PALETTE_DRAG_TYPE)) return;
1861
+ event.preventDefault();
1862
+ event.dataTransfer.dropEffect = "copy";
1863
+ };
1864
+ const onDrop = (event) => {
1865
+ if (!editable) return;
1866
+ const kind = event.dataTransfer.getData(PALETTE_DRAG_TYPE);
1867
+ if (!NODE_KINDS.includes(kind)) return;
1868
+ event.preventDefault();
1869
+ const point = rf.screenToFlowPosition({ x: event.clientX, y: event.clientY });
1870
+ const id = addNodeAt(kind, point);
1871
+ if (id !== void 0) startEditing({ kind: "node", id });
1872
+ };
1873
+ const api = {
1874
+ fitView: fit,
1875
+ zoomIn,
1876
+ zoomOut,
1877
+ focusNode,
1878
+ select,
1879
+ clearSelection,
1880
+ undo,
1881
+ redo,
1882
+ applyLayout: applyAutoLayout,
1883
+ addNode: (kind, init) => addNodeAt(kind, viewCenter(), init),
1884
+ align,
1885
+ distribute,
1886
+ group,
1887
+ ungroup,
1888
+ duplicate,
1889
+ deleteSelection,
1890
+ copy,
1891
+ paste,
1892
+ getDocument: () => docRef.current,
1893
+ getSelection: () => ({ nodeIds: [...selectedNodes], edgeIds: [...selectedEdges] })
1894
+ };
1895
+ const apiRef = useRef4(api);
1896
+ apiRef.current = api;
1897
+ useImperativeHandle(
1898
+ ref,
1899
+ () => {
1900
+ const call = apiRef;
1901
+ return {
1902
+ fitView: () => call.current.fitView(),
1903
+ zoomIn: () => call.current.zoomIn(),
1904
+ zoomOut: () => call.current.zoomOut(),
1905
+ focusNode: (id) => call.current.focusNode(id),
1906
+ select: (nodeIds, edgeIds) => call.current.select(nodeIds, edgeIds),
1907
+ clearSelection: () => call.current.clearSelection(),
1908
+ undo: () => call.current.undo(),
1909
+ redo: () => call.current.redo(),
1910
+ applyLayout: (options) => call.current.applyLayout(options),
1911
+ addNode: (kind, init) => call.current.addNode(kind, init),
1912
+ align: (alignment) => call.current.align(alignment),
1913
+ distribute: (axis) => call.current.distribute(axis),
1914
+ group: () => call.current.group(),
1915
+ ungroup: () => call.current.ungroup(),
1916
+ duplicate: () => call.current.duplicate(),
1917
+ deleteSelection: () => call.current.deleteSelection(),
1918
+ copy: () => call.current.copy(),
1919
+ paste: () => call.current.paste(),
1920
+ getDocument: () => call.current.getDocument(),
1921
+ getSelection: () => call.current.getSelection()
1922
+ };
1923
+ },
1924
+ []
1925
+ );
1926
+ const markerId = useCallback2((tone) => `${rootId}-arrow-${tone}`, [rootId]);
1927
+ const context = useMemo3(
1928
+ () => ({
1929
+ messages,
1930
+ editable,
1931
+ rtl,
1932
+ direction,
1933
+ edgeKind,
1934
+ renderers: nodeRenderers ?? {},
1935
+ editing,
1936
+ connectingFrom,
1937
+ startEditing,
1938
+ commitEditing,
1939
+ cancelEditing,
1940
+ toggleCollapsed,
1941
+ commitResize,
1942
+ markerId
1943
+ }),
1944
+ [messages, editable, rtl, direction, edgeKind, nodeRenderers, editing, connectingFrom, startEditing, commitEditing, cancelEditing, toggleCollapsed, commitResize, markerId]
1945
+ );
1946
+ const ariaLabelConfig = useMemo3(
1947
+ () => ({
1948
+ "node.a11yDescription.default": "",
1949
+ "node.a11yDescription.keyboardDisabled": "",
1950
+ "node.a11yDescription.ariaLiveMessage": () => "",
1951
+ "edge.a11yDescription.default": "",
1952
+ "minimap.ariaLabel": formatMessage(messages.minimap),
1953
+ "handle.ariaLabel": formatMessage(messages.node.port)
1954
+ }),
1955
+ [messages]
1956
+ );
1957
+ const toolbarSections = useMemo3(() => {
1958
+ const all = { palette: true, history: true, edit: true, layout: true, view: true };
1959
+ if (toolbar === false) return void 0;
1960
+ return toolbar === true ? all : { ...all, ...toolbar };
1961
+ }, [toolbar]);
1962
+ const accessibleName = label ?? formatMessage(messages.canvas.label, { title: doc.title });
1963
+ const hostStyle = { ...height === void 0 ? {} : { "--diagram-height": typeof height === "number" ? `${height}px` : height }, ...style };
1964
+ const empty = viewDoc.nodes.length === 0;
1965
+ return /* @__PURE__ */ jsx6(CanvasContext.Provider, { value: context, children: /* @__PURE__ */ jsxs6(
1966
+ "div",
1967
+ {
1968
+ ...rest,
1969
+ ref: hostRef,
1970
+ id: rootId,
1971
+ tabIndex: -1,
1972
+ "data-kit": "diagram-canvas",
1973
+ "data-readonly": readOnly ? "" : void 0,
1974
+ "data-rtl": rtl ? "" : void 0,
1975
+ "data-connecting": connectingFrom === void 0 ? void 0 : "",
1976
+ dir: rtl ? "rtl" : "ltr",
1977
+ className,
1978
+ style: hostStyle,
1979
+ children: [
1980
+ /* @__PURE__ */ jsx6(EdgeMarkers, { markerId }),
1981
+ toolbarSections === void 0 ? null : /* @__PURE__ */ jsx6(
1982
+ DiagramToolbar,
1983
+ {
1984
+ messages,
1985
+ editable,
1986
+ sections: { ...toolbarSections, history: toolbarSections.history && historySource !== void 0 },
1987
+ palette,
1988
+ rtl,
1989
+ paletteOpen,
1990
+ onPaletteOpenChange: setPaletteOpen,
1991
+ state: {
1992
+ canUndo: historySource?.canUndo ?? false,
1993
+ canRedo: historySource?.canRedo ?? false,
1994
+ hasSelection: selectedNodes.size + selectedEdges.size > 0,
1995
+ canGroup,
1996
+ canUngroup: ungroupable.length > 0,
1997
+ minimap: showMiniMap,
1998
+ grid: showGrid
1999
+ },
2000
+ actions: {
2001
+ addNode: addFromPalette,
2002
+ undo,
2003
+ redo,
2004
+ deleteSelection,
2005
+ duplicate,
2006
+ group,
2007
+ ungroup,
2008
+ layout: () => applyAutoLayout(),
2009
+ zoomIn,
2010
+ zoomOut,
2011
+ fitView: fit,
2012
+ toggleMiniMap: () => setShowMiniMap((value2) => !value2),
2013
+ toggleGrid: () => setShowGrid((value2) => !value2)
2014
+ }
2015
+ }
2016
+ ),
2017
+ /* @__PURE__ */ jsxs6("div", { ref: stageRef, "data-slot": "stage", onDragOver, onDrop, children: [
2018
+ /* @__PURE__ */ jsx6(VisuallyHidden, { id: instructionsId, children: formatMessage(editable ? messages.canvas.instructions : messages.canvas.instructionsReadOnly) }),
2019
+ /* @__PURE__ */ jsxs6(
2020
+ ReactFlow,
2021
+ {
2022
+ nodes: flowNodes,
2023
+ edges: flowEdges,
2024
+ nodeTypes: NODE_TYPES,
2025
+ edgeTypes: EDGE_TYPES,
2026
+ onNodesChange,
2027
+ onEdgesChange,
2028
+ onConnect,
2029
+ onConnectEnd,
2030
+ isValidConnection,
2031
+ onNodeDragStop,
2032
+ onNodeDoubleClick: (_event, node) => {
2033
+ if (editable) startEditing({ kind: "node", id: node.id });
2034
+ else activate(node.id);
2035
+ },
2036
+ onEdgeDoubleClick: (_event, edge) => startEditing({ kind: "edge", id: edge.id }),
2037
+ nodesDraggable: editable,
2038
+ nodesConnectable: editable,
2039
+ nodesFocusable: true,
2040
+ edgesFocusable,
2041
+ edgesReconnectable: false,
2042
+ elementsSelectable: true,
2043
+ deleteKeyCode: null,
2044
+ disableKeyboardA11y: true,
2045
+ snapToGrid: snap > 0,
2046
+ snapGrid: [snap > 0 ? snap : GRID_SIZE, snap > 0 ? snap : GRID_SIZE],
2047
+ minZoom,
2048
+ maxZoom,
2049
+ zoomOnScroll: wheelZoom,
2050
+ preventScrolling: wheelZoom,
2051
+ onlyRenderVisibleElements: virtualize,
2052
+ fitView: fitOnMount,
2053
+ fitViewOptions: FIT_OPTIONS,
2054
+ proOptions: { hideAttribution: !showAttribution },
2055
+ ariaLabelConfig,
2056
+ onError: (code, message) => {
2057
+ if (code !== "013") console.warn(message);
2058
+ },
2059
+ "aria-label": accessibleName,
2060
+ "aria-describedby": instructionsId,
2061
+ "data-kit": "diagram-flow",
2062
+ onKeyDown,
2063
+ children: [
2064
+ showGrid ? /* @__PURE__ */ jsx6(Background, { variant: BackgroundVariant.Dots, gap: GRID_SIZE * 2, size: 1 }) : null,
2065
+ showMiniMap ? /* @__PURE__ */ jsx6(
2066
+ MiniMap,
2067
+ {
2068
+ pannable: true,
2069
+ zoomable: true,
2070
+ ariaLabel: formatMessage(messages.minimap),
2071
+ position: rtl ? "bottom-left" : "bottom-right",
2072
+ nodeClassName: (node) => {
2073
+ const data = node.data;
2074
+ return `kit-minimap-node ${data?.node.status ?? "neutral"} ${data?.node.kind ?? ""}`.trim();
2075
+ }
2076
+ }
2077
+ ) : null
2078
+ ]
2079
+ }
2080
+ ),
2081
+ empty ? /* @__PURE__ */ jsx6("p", { "data-slot": "empty", dir: "auto", children: formatMessage(messages.canvas.empty) }) : null,
2082
+ /* @__PURE__ */ jsx6("div", { "data-kit": "diagram-notice", role: "status", children: notice === void 0 ? null : /* @__PURE__ */ jsx6("span", { "data-slot": "message", dir: "auto", children: notice.text }, notice.n) })
2083
+ ] }),
2084
+ /* @__PURE__ */ jsx6(VisuallyHidden, { role: "status", "aria-live": "polite", "aria-atomic": "true", "data-kit": "diagram-live", children: /* @__PURE__ */ jsx6("span", { children: announcement.text }, announcement.n) }),
2085
+ outline === false ? null : /* @__PURE__ */ jsx6(
2086
+ DiagramOutline,
2087
+ {
2088
+ document: viewDoc,
2089
+ messages: messagesOverride ?? {},
2090
+ direction,
2091
+ rtl,
2092
+ reveal: outline === "visible" ? "always" : "focus",
2093
+ onNavigate: (nodeId) => void focusNode(nodeId),
2094
+ ...editable ? {
2095
+ onDisconnect: (edgeId) => {
2096
+ const edge = docRef.current.edges.find((candidate) => candidate.id === edgeId);
2097
+ if (!edge) return;
2098
+ commit(disconnect(docRef.current, edgeId), { reason: "disconnect", edgeIds: [edgeId], nodeIds: [edge.source, edge.target] });
2099
+ announce(formatMessage(messages.announce.disconnected, { source: labelOfNode(edge.source), target: labelOfNode(edge.target) }));
2100
+ }
2101
+ } : {}
2102
+ }
2103
+ )
2104
+ ]
2105
+ }
2106
+ ) });
2107
+ }
2108
+
2109
+ // src/react/mermaid.tsx
2110
+ import { Button as Button2, Spinner } from "@x12i/ui";
2111
+ import { useEffect as useEffect4, useId as useId4, useMemo as useMemo4, useRef as useRef5, useState as useState4 } from "react";
2112
+
2113
+ // src/react/dom.ts
2114
+ function downloadBlob(content, filename, type = "text/plain;charset=utf-8") {
2115
+ const blob = typeof content === "string" ? new Blob([content], { type }) : content;
2116
+ const url = URL.createObjectURL(blob);
2117
+ const link = document.createElement("a");
2118
+ link.href = url;
2119
+ link.download = filename;
2120
+ link.rel = "noopener";
2121
+ link.style.display = "none";
2122
+ document.body.append(link);
2123
+ link.click();
2124
+ link.remove();
2125
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
2126
+ }
2127
+ var VAR = /var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*))?\)/g;
2128
+ function resolveCssVariables(text, element) {
2129
+ if (typeof document === "undefined") return text;
2130
+ const style = getComputedStyle(element ?? document.documentElement);
2131
+ let current = text;
2132
+ for (let pass = 0; pass < 5; pass += 1) {
2133
+ const next = current.replace(VAR, (match, name, fallback) => {
2134
+ const value = style.getPropertyValue(name).trim();
2135
+ if (value !== "") return value;
2136
+ return fallback === void 0 ? match : fallback.trim();
2137
+ });
2138
+ if (next === current) break;
2139
+ current = next;
2140
+ }
2141
+ return current;
2142
+ }
2143
+ function readDiagramPalette(element) {
2144
+ const resolve = (value) => resolveCssVariables(value, element);
2145
+ const status = {};
2146
+ for (const key of Object.keys(defaultPalette.status)) {
2147
+ const tone = defaultPalette.status[key];
2148
+ status[key] = { background: resolve(tone.background), foreground: resolve(tone.foreground), border: resolve(tone.border) };
2149
+ }
2150
+ const palette = { ...defaultPalette, status };
2151
+ for (const [key, value] of Object.entries(defaultPalette)) {
2152
+ if (typeof value === "string") palette[key] = resolve(value);
2153
+ }
2154
+ return palette;
2155
+ }
2156
+ function downloadDiagramSvg(doc, options = {}) {
2157
+ const { filename = "diagram.svg", element, ...svgOptions } = options;
2158
+ const svg = resolveCssVariables(exportDiagramSvg(doc, svgOptions), element);
2159
+ downloadBlob(svg, filename, "image/svg+xml;charset=utf-8");
2160
+ }
2161
+ function downloadDiagramJson(doc, filename = "diagram.json") {
2162
+ downloadBlob(exportDiagramJson(doc), filename, "application/json;charset=utf-8");
2163
+ }
2164
+
2165
+ // src/react/sanitize.ts
2166
+ var DROP_SVG = /* @__PURE__ */ new Set([
2167
+ "script",
2168
+ "iframe",
2169
+ "object",
2170
+ "embed",
2171
+ "link",
2172
+ "meta",
2173
+ "base",
2174
+ "audio",
2175
+ "video",
2176
+ "canvas",
2177
+ "animate",
2178
+ "animatetransform",
2179
+ "animatemotion",
2180
+ "set",
2181
+ "discard",
2182
+ "handler",
2183
+ "listener"
2184
+ ]);
2185
+ var HTML_TEXT = /* @__PURE__ */ new Set(["div", "span", "p", "br", "b", "i", "u", "em", "strong", "code", "pre", "small", "sub", "sup", "ul", "ol", "li", "body"]);
2186
+ var DROP_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc", "formaction", "action", "ping"]);
2187
+ function isHtml(element) {
2188
+ return element.namespaceURI === "http://www.w3.org/1999/xhtml";
2189
+ }
2190
+ function cleanStyleText(css) {
2191
+ return css.replace(/@import[^;]*;?/gi, "").replace(/expression\s*\([^)]*\)/gi, "none").replace(/javascript:/gi, "").replace(/url\(\s*(?!['"]?#)[^)]*\)/gi, "none");
2192
+ }
2193
+ function sanitizeElement(element) {
2194
+ for (const child of Array.from(element.children)) {
2195
+ const name = child.localName.toLowerCase();
2196
+ const html = isHtml(child);
2197
+ if (!html && DROP_SVG.has(name) || html && !HTML_TEXT.has(name)) {
2198
+ if (html && name !== "script" && name !== "style" && name !== "iframe" && name !== "object" && name !== "embed") {
2199
+ sanitizeElement(child);
2200
+ child.replaceWith(...Array.from(child.childNodes));
2201
+ } else {
2202
+ child.remove();
2203
+ }
2204
+ continue;
2205
+ }
2206
+ for (const attribute of Array.from(child.attributes)) {
2207
+ const attributeName2 = attribute.name.toLowerCase();
2208
+ const value = attribute.value;
2209
+ if (attributeName2.startsWith("on") || DROP_ATTRIBUTES.has(attributeName2)) {
2210
+ child.removeAttribute(attribute.name);
2211
+ } else if (attributeName2 === "href" || attributeName2 === "xlink:href" || attributeName2 === "src") {
2212
+ const safeFragment = value.trim().startsWith("#");
2213
+ const safeImage = name === "image" && isSafeImageUrl(value.trim());
2214
+ if (!safeFragment && !safeImage) child.removeAttribute(attribute.name);
2215
+ } else if (attributeName2 === "style") {
2216
+ const cleaned = cleanStyleText(value);
2217
+ if (cleaned !== value) child.setAttribute(attribute.name, cleaned);
2218
+ } else if (/^\s*javascript:/i.test(value)) {
2219
+ child.removeAttribute(attribute.name);
2220
+ }
2221
+ }
2222
+ if (name === "style") child.textContent = cleanStyleText(child.textContent ?? "");
2223
+ sanitizeElement(child);
2224
+ }
2225
+ }
2226
+ function sanitizeSvgMarkup(markup) {
2227
+ if (typeof DOMParser === "undefined") return null;
2228
+ const parsed = new DOMParser().parseFromString(`<!doctype html><body>${markup}</body>`, "text/html");
2229
+ const root = parsed.body.querySelector("svg");
2230
+ if (!root) return null;
2231
+ const holder = parsed.createElement("div");
2232
+ holder.append(root);
2233
+ sanitizeElement(holder);
2234
+ for (const attribute of Array.from(root.attributes)) {
2235
+ if (attribute.name.toLowerCase().startsWith("on")) root.removeAttribute(attribute.name);
2236
+ else if (attribute.name.toLowerCase() === "style") root.setAttribute(attribute.name, cleanStyleText(attribute.value));
2237
+ }
2238
+ return root;
2239
+ }
2240
+
2241
+ // src/react/mermaid.tsx
2242
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2243
+ function isApi(value) {
2244
+ return typeof value === "object" && value !== null && typeof value.render === "function" && typeof value.initialize === "function";
2245
+ }
2246
+ function normalize(loaded) {
2247
+ if (isApi(loaded)) return loaded;
2248
+ const fallback = loaded.default;
2249
+ if (isApi(fallback)) return fallback;
2250
+ throw new Error("The loader did not return Mermaid (an object with initialize and render).");
2251
+ }
2252
+ var TOKENS = {
2253
+ background: "--kit-color-bg-surface",
2254
+ canvas: "--kit-color-bg-canvas",
2255
+ text: "--kit-color-text-primary",
2256
+ muted: "--kit-color-text-secondary",
2257
+ border: "--kit-color-border-default",
2258
+ borderStrong: "--kit-color-border-strong",
2259
+ accent: "--kit-color-accent-default",
2260
+ accentSubtle: "--kit-color-accent-subtle",
2261
+ warningBackground: "--kit-color-status-warning-background",
2262
+ warningForeground: "--kit-color-status-warning-foreground",
2263
+ warningBorder: "--kit-color-status-warning-border",
2264
+ dangerBackground: "--kit-color-status-danger-background",
2265
+ dangerForeground: "--kit-color-status-danger-foreground",
2266
+ font: "--kit-font-family-sans"
2267
+ };
2268
+ function readKitThemeVariables(element) {
2269
+ const read = (name) => resolveCssVariables(`var(${name})`, element);
2270
+ const t = Object.fromEntries(Object.entries(TOKENS).map(([key, name]) => [key, read(name)]));
2271
+ return {
2272
+ background: t.background,
2273
+ fontFamily: t.font,
2274
+ primaryColor: t.accentSubtle,
2275
+ primaryTextColor: t.text,
2276
+ primaryBorderColor: t.accent,
2277
+ secondaryColor: t.canvas,
2278
+ secondaryTextColor: t.text,
2279
+ secondaryBorderColor: t.border,
2280
+ tertiaryColor: t.background,
2281
+ tertiaryTextColor: t.text,
2282
+ tertiaryBorderColor: t.border,
2283
+ lineColor: t.borderStrong,
2284
+ textColor: t.text,
2285
+ mainBkg: t.background,
2286
+ nodeBorder: t.border,
2287
+ nodeTextColor: t.text,
2288
+ clusterBkg: t.canvas,
2289
+ clusterBorder: t.border,
2290
+ titleColor: t.text,
2291
+ edgeLabelBackground: t.background,
2292
+ noteBkgColor: t.warningBackground,
2293
+ noteTextColor: t.warningForeground,
2294
+ noteBorderColor: t.warningBorder,
2295
+ errorBkgColor: t.dangerBackground,
2296
+ errorTextColor: t.dangerForeground,
2297
+ actorBkg: t.background,
2298
+ actorBorder: t.border,
2299
+ actorTextColor: t.text,
2300
+ signalColor: t.text,
2301
+ signalTextColor: t.text,
2302
+ labelBoxBkgColor: t.background,
2303
+ labelTextColor: t.text
2304
+ };
2305
+ }
2306
+ var queue = Promise.resolve();
2307
+ function enqueue(job) {
2308
+ const run = queue.then(job, job);
2309
+ queue = run.catch(() => void 0);
2310
+ return run;
2311
+ }
2312
+ function readThemeSignature() {
2313
+ if (typeof document === "undefined") return "";
2314
+ const root = document.documentElement;
2315
+ return `${root.getAttribute("data-theme") ?? ""}|${root.getAttribute("class") ?? ""}|${root.getAttribute("style") ?? ""}`;
2316
+ }
2317
+ function MermaidDiagram({
2318
+ source,
2319
+ loadMermaid,
2320
+ title,
2321
+ description,
2322
+ messages: messagesOverride,
2323
+ config,
2324
+ onRender,
2325
+ onError,
2326
+ showSourceToggle = true,
2327
+ className,
2328
+ ...props
2329
+ }) {
2330
+ const messages = useMemo4(() => resolveMessages(messagesOverride), [messagesOverride]);
2331
+ const host = useRef5(null);
2332
+ const uid = useId4().replace(/[^a-zA-Z0-9_-]/g, "");
2333
+ const sourceId = useId4();
2334
+ const [state, setState] = useState4({ status: "loading" });
2335
+ const [sourceShown, setSourceShown] = useState4(false);
2336
+ const [themeSignature, setThemeSignature] = useState4(readThemeSignature);
2337
+ const renders = useRef5(0);
2338
+ const latest = useRef5({ loadMermaid, onRender, onError });
2339
+ latest.current = { loadMermaid, onRender, onError };
2340
+ const configKey = useMemo4(() => JSON.stringify(config ?? {}), [config]);
2341
+ useEffect4(() => {
2342
+ if (typeof MutationObserver === "undefined") return;
2343
+ const observer = new MutationObserver(() => setThemeSignature(readThemeSignature()));
2344
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "class", "style"] });
2345
+ return () => observer.disconnect();
2346
+ }, []);
2347
+ useEffect4(() => {
2348
+ let cancelled = false;
2349
+ setState({ status: "loading" });
2350
+ const id = `${uid}-m${renders.current += 1}`;
2351
+ const run = async () => {
2352
+ let api;
2353
+ try {
2354
+ api = normalize(await latest.current.loadMermaid());
2355
+ } catch (error) {
2356
+ if (cancelled) return;
2357
+ setState({ status: "error", kind: "load", detail: error instanceof Error ? error.message : String(error) });
2358
+ latest.current.onError?.(error, "load");
2359
+ return;
2360
+ }
2361
+ if (cancelled) return;
2362
+ try {
2363
+ const user = JSON.parse(configKey);
2364
+ const result = await enqueue(async () => {
2365
+ if (cancelled) return void 0;
2366
+ api.initialize({
2367
+ theme: "base",
2368
+ themeVariables: readKitThemeVariables(host.current ?? void 0),
2369
+ htmlLabels: false,
2370
+ flowchart: { htmlLabels: false },
2371
+ suppressErrorRendering: true,
2372
+ ...user,
2373
+ // The two settings that protect the page cannot be changed by props.
2374
+ startOnLoad: false,
2375
+ securityLevel: "strict"
2376
+ });
2377
+ return api.render(id, source);
2378
+ });
2379
+ if (cancelled || result === void 0) return;
2380
+ const svg = sanitizeSvgMarkup(result.svg);
2381
+ if (!svg) throw new Error("Mermaid returned no SVG.");
2382
+ svg.setAttribute("aria-hidden", "true");
2383
+ svg.setAttribute("focusable", "false");
2384
+ svg.removeAttribute("width");
2385
+ svg.style.maxWidth = "100%";
2386
+ svg.style.height = "auto";
2387
+ host.current?.replaceChildren(document.importNode(svg, true));
2388
+ setState({ status: "ready" });
2389
+ latest.current.onRender?.(svg);
2390
+ } catch (error) {
2391
+ if (cancelled) return;
2392
+ host.current?.replaceChildren();
2393
+ setState({ status: "error", kind: "render", detail: error instanceof Error ? error.message : String(error) });
2394
+ latest.current.onError?.(error, "render");
2395
+ } finally {
2396
+ document.getElementById(id)?.remove();
2397
+ document.getElementById(`d${id}`)?.remove();
2398
+ }
2399
+ };
2400
+ void run();
2401
+ return () => {
2402
+ cancelled = true;
2403
+ };
2404
+ }, [source, configKey, themeSignature, uid]);
2405
+ const failed = state.status === "error";
2406
+ return /* @__PURE__ */ jsxs7("figure", { "data-kit": "mermaid-diagram", "data-status": state.status, className, ...props, children: [
2407
+ /* @__PURE__ */ jsx7(
2408
+ "div",
2409
+ {
2410
+ ref: host,
2411
+ "data-slot": "canvas",
2412
+ role: "img",
2413
+ "aria-label": title,
2414
+ "aria-describedby": description === void 0 ? void 0 : `${sourceId}-description`,
2415
+ hidden: state.status !== "ready"
2416
+ }
2417
+ ),
2418
+ state.status === "loading" ? /* @__PURE__ */ jsxs7("div", { "data-slot": "status", role: "status", children: [
2419
+ /* @__PURE__ */ jsx7(Spinner, { size: "sm" }),
2420
+ /* @__PURE__ */ jsx7("span", { children: formatMessage(messages.mermaid.loading) })
2421
+ ] }) : null,
2422
+ failed ? /* @__PURE__ */ jsx7("div", { "data-slot": "status", role: "alert", children: /* @__PURE__ */ jsx7("span", { children: formatMessage(state.kind === "load" ? messages.mermaid.loadError : messages.mermaid.renderError) }) }) : null,
2423
+ description === void 0 ? null : /* @__PURE__ */ jsx7("figcaption", { id: `${sourceId}-description`, "data-slot": "description", dir: "auto", children: description }),
2424
+ showSourceToggle || failed ? /* @__PURE__ */ jsxs7("div", { "data-slot": "source", children: [
2425
+ failed ? null : /* @__PURE__ */ jsx7(
2426
+ Button2,
2427
+ {
2428
+ variant: "ghost",
2429
+ tone: "neutral",
2430
+ size: "sm",
2431
+ "aria-expanded": sourceShown,
2432
+ "aria-controls": `${sourceId}-source`,
2433
+ onClick: () => setSourceShown((value) => !value),
2434
+ children: formatMessage(sourceShown ? messages.mermaid.hideSource : messages.mermaid.showSource)
2435
+ }
2436
+ ),
2437
+ sourceShown || failed ? /* @__PURE__ */ jsx7("pre", { id: `${sourceId}-source`, dir: "ltr", tabIndex: 0, "aria-label": formatMessage(messages.mermaid.sourceLabel), children: /* @__PURE__ */ jsx7("code", { children: source }) }) : null
2438
+ ] }) : null
2439
+ ] });
2440
+ }
2441
+
2442
+ // src/react/static.tsx
2443
+ import { createElement as createElement2, useId as useId5, useMemo as useMemo5 } from "react";
2444
+ var ATTRIBUTE_NAMES = {
2445
+ class: "className",
2446
+ "xlink:href": "xlinkHref",
2447
+ "xml:space": "xmlSpace",
2448
+ "xml:lang": "xmlLang"
2449
+ };
2450
+ function attributeName(name) {
2451
+ const mapped = ATTRIBUTE_NAMES[name];
2452
+ if (mapped !== void 0) return mapped;
2453
+ if (name.startsWith("aria-") || name.startsWith("data-") || !name.includes("-")) return name;
2454
+ return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
2455
+ }
2456
+ function styleObject(style) {
2457
+ if (!style) return void 0;
2458
+ const result = {};
2459
+ for (const [name, value] of Object.entries(style)) {
2460
+ result[name.startsWith("--") ? name : name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())] = value;
2461
+ }
2462
+ return result;
2463
+ }
2464
+ function toReact(element, key, extra = {}) {
2465
+ const props = { key };
2466
+ for (const [name, value] of Object.entries(element.attrs ?? {})) {
2467
+ if (value !== void 0) props[attributeName(name)] = value;
2468
+ }
2469
+ const style = styleObject(element.style);
2470
+ if (style !== void 0) props.style = style;
2471
+ Object.assign(props, extra);
2472
+ const children = (element.children ?? []).map((child, index) => typeof child === "string" ? child : toReact(child, index));
2473
+ return createElement2(element.tag, props, ...children);
2474
+ }
2475
+ function StaticDiagram({ document: doc, messages, idPrefix, className, style, ...options }) {
2476
+ const generated = useId5().replace(/[^a-zA-Z0-9_-]/g, "");
2477
+ const connector = options.connector ?? formatMessage(resolveMessages(messages).outline.connector);
2478
+ const tree = useMemo5(
2479
+ () => diagramToSvgTree(doc, { ...options, connector, idPrefix: idPrefix ?? `kit-diagram-${generated}` }),
2480
+ // `options` is a fresh object each render; list its fields instead.
2481
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2482
+ [
2483
+ doc,
2484
+ connector,
2485
+ idPrefix,
2486
+ generated,
2487
+ options.direction,
2488
+ options.rtl,
2489
+ options.edgeKind,
2490
+ options.padding,
2491
+ options.palette,
2492
+ options.title,
2493
+ options.description,
2494
+ options.lang,
2495
+ options.width,
2496
+ options.height,
2497
+ options.highlight,
2498
+ options.showEdgeLabels
2499
+ ]
2500
+ );
2501
+ const extra = {};
2502
+ if (className !== void 0) extra.className = className;
2503
+ if (style !== void 0) extra.style = { ...styleObject(tree.style), ...style };
2504
+ return toReact(tree, void 0, extra);
2505
+ }
2506
+ export {
2507
+ DiagramCanvas,
2508
+ DiagramOutline,
2509
+ MermaidDiagram,
2510
+ PALETTE_DRAG_TYPE,
2511
+ StaticDiagram,
2512
+ defaultMessages,
2513
+ downloadBlob,
2514
+ downloadDiagramJson,
2515
+ downloadDiagramSvg,
2516
+ exportDiagramJson,
2517
+ exportDiagramSvg,
2518
+ focusDiagramNode,
2519
+ readDiagramPalette,
2520
+ readKitThemeVariables,
2521
+ resolveCssVariables,
2522
+ sanitizeSvgMarkup,
2523
+ useDiagramHistory
2524
+ };
2525
+ //# sourceMappingURL=react.js.map