flowchart-sequence-designer 1.0.0 → 1.0.1

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/ui/index.cjs CHANGED
@@ -31,7 +31,7 @@ __export(ui_exports, {
31
31
  module.exports = __toCommonJS(ui_exports);
32
32
 
33
33
  // src/ui/DiagramEditor.tsx
34
- var import_react17 = require("react");
34
+ var import_react19 = require("react");
35
35
 
36
36
  // src/ui/Toolbar.tsx
37
37
  var import_react2 = require("react");
@@ -407,6 +407,12 @@ var ACCENT = {
407
407
  emeraldDarkLight: "rgba(16,185,129,0.12)",
408
408
  emeraldDarkBorder: "rgba(16,185,129,0.3)"
409
409
  };
410
+ function shadowColor(isDark) {
411
+ return isDark ? "rgba(0,0,0,0.55)" : "rgba(15,23,42,0.09)";
412
+ }
413
+ function arrowColor(isDark) {
414
+ return isDark ? "#64748b" : "#94a3b8";
415
+ }
410
416
  function variantAccent(variant, isDark) {
411
417
  if (variant === "question") {
412
418
  return isDark ? { color: ACCENT.amberDark, fill: ACCENT.amberDarkLight, border: ACCENT.amberDarkBorder, glow: ACCENT.amberGlow } : { color: ACCENT.amber, fill: ACCENT.amberLight, border: ACCENT.amberBorder, glow: ACCENT.amberGlow };
@@ -839,18 +845,278 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
839
845
  }
840
846
 
841
847
  // src/ui/SequenceEditor.tsx
842
- var import_react8 = require("react");
848
+ var import_react10 = require("react");
849
+
850
+ // src/ui/SequenceCanvas.tsx
851
+ var import_react4 = require("react");
852
+ var import_jsx_runtime4 = require("react/jsx-runtime");
853
+ var HEADER_H = 64;
854
+ var HEADER_PAD = 24;
855
+ var ROW_H = 64;
856
+ var SIDE_PAD = 40;
857
+ var INDIGO = "#4f46e5";
858
+ var INDIGO_SOFT = "#eef2ff";
859
+ var STYLE_SEQ_GRAB = { cursor: "grab" };
860
+ var STYLE_SEQ_GRABBING = { cursor: "grabbing" };
861
+ var STYLE_SEQ_ACTOR_TEXT = { cursor: "pointer", userSelect: "none" };
862
+ var STYLE_SEQ_REMOVE_BTN = { cursor: "pointer" };
863
+ var STYLE_SEQ_REMOVE_ICON = { pointerEvents: "none", userSelect: "none" };
864
+ var STYLE_SEQ_DRAGGING = { opacity: 0.85 };
865
+ function estimateW(text, pxPerChar = 7) {
866
+ return text.length * pxPerChar;
867
+ }
868
+ function SequenceCanvas(props) {
869
+ const {
870
+ model,
871
+ actors,
872
+ messages,
873
+ t,
874
+ isDark,
875
+ colW,
876
+ totalW,
877
+ totalH,
878
+ actorX,
879
+ msgY,
880
+ selected,
881
+ editingId,
882
+ setEditingId,
883
+ drag,
884
+ onRowMouseDown,
885
+ renameActor,
886
+ removeActor,
887
+ svgRef
888
+ } = props;
889
+ const visualMessages = (0, import_react4.useMemo)(() => {
890
+ if (!drag?.active) return messages;
891
+ const idx = messages.findIndex((m) => m.id === drag.id);
892
+ if (idx < 0) return messages;
893
+ const next = messages.slice();
894
+ const [moved] = next.splice(idx, 1);
895
+ next.splice(drag.targetIdx, 0, moved);
896
+ return next;
897
+ }, [messages, drag]);
898
+ if (actors.length === 0 && messages.length === 0) {
899
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
900
+ position: "absolute",
901
+ inset: 0,
902
+ display: "flex",
903
+ flexDirection: "column",
904
+ alignItems: "center",
905
+ justifyContent: "center",
906
+ gap: 10,
907
+ color: t.textMuted,
908
+ pointerEvents: "none"
909
+ }, children: [
910
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 36, opacity: 0.15, color: t.textPrimary }, children: "\u2194" }),
911
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
912
+ "Click ",
913
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Actor" }),
914
+ " then ",
915
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Message" }),
916
+ " to start"
917
+ ] })
918
+ ] });
919
+ }
920
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
921
+ "svg",
922
+ {
923
+ ref: svgRef,
924
+ width: totalW,
925
+ height: totalH,
926
+ style: { display: "block", cursor: drag?.active ? "grabbing" : "default", userSelect: "none" },
927
+ children: [
928
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("defs", { children: [
929
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("pattern", { id: "seqdots", x: "0", y: "0", width: "24", height: "24", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: 12, cy: 12, r: 1.1, fill: t.dot }) }),
930
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("filter", { id: "seqShadow", x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("feDropShadow", { dx: 0, dy: 3, stdDeviation: 5, floodColor: shadowColor(isDark) }) }),
931
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("marker", { id: "seqArrow", markerWidth: 9, markerHeight: 7, refX: 8.5, refY: 3.5, orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: t.arrow }) })
932
+ ] }),
933
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: totalW, height: totalH, fill: "url(#seqdots)" }),
934
+ actors.map((name) => {
935
+ const x = actorX(name);
936
+ const top = HEADER_PAD + HEADER_H;
937
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
938
+ "line",
939
+ {
940
+ x1: x,
941
+ x2: x,
942
+ y1: top + 4,
943
+ y2: totalH - 24,
944
+ stroke: t.lifeline,
945
+ strokeWidth: 1.25,
946
+ strokeDasharray: "5 5"
947
+ },
948
+ `life-${name}`
949
+ );
950
+ }),
951
+ visualMessages.map((msg, idx) => {
952
+ const y = msgY(idx);
953
+ const fromX = actorX(msg.from);
954
+ const toX = actorX(msg.to);
955
+ const selectedHere = selected === msg.id;
956
+ const isDragging = drag?.active && drag.id === msg.id;
957
+ const isSelf = msg.from === msg.to;
958
+ const stroke = selectedHere ? INDIGO : t.arrow;
959
+ const dash = msg.style === "dashed" ? "6,4" : void 0;
960
+ const cursorStyle = drag?.active ? STYLE_SEQ_GRABBING : STYLE_SEQ_GRAB;
961
+ const groupStyle = isDragging ? { ...cursorStyle, ...STYLE_SEQ_DRAGGING } : cursorStyle;
962
+ if (isSelf) {
963
+ const startX = fromX;
964
+ const loopW = 36;
965
+ const loopY = y - 6;
966
+ const d = `M ${startX} ${loopY} C ${startX + loopW} ${loopY}, ${startX + loopW} ${loopY + 24}, ${startX} ${loopY + 24}`;
967
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: groupStyle, children: [
968
+ (selectedHere || isDragging) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
969
+ "rect",
970
+ {
971
+ x: SIDE_PAD - 8,
972
+ y: y - 22,
973
+ width: totalW - (SIDE_PAD - 8) * 2,
974
+ height: ROW_H - 12,
975
+ rx: 10,
976
+ fill: INDIGO_SOFT,
977
+ opacity: isDark ? 0.18 : 0.6
978
+ }
979
+ ),
980
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d, fill: "none", stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
981
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: startX + loopW + 8, y: loopY + 16, fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
982
+ ] }, msg.id);
983
+ }
984
+ const labelX = (fromX + toX) / 2;
985
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: groupStyle, children: [
986
+ (selectedHere || isDragging) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
987
+ "rect",
988
+ {
989
+ x: SIDE_PAD - 8,
990
+ y: y - 22,
991
+ width: totalW - (SIDE_PAD - 8) * 2,
992
+ height: ROW_H - 12,
993
+ rx: 10,
994
+ fill: INDIGO_SOFT,
995
+ opacity: isDark ? 0.18 : 0.6
996
+ }
997
+ ),
998
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: fromX, y1: y, x2: toX, y2: y, stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
999
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1000
+ "rect",
1001
+ {
1002
+ x: labelX - estimateW(msg.label) / 2 - 6,
1003
+ y: y - 18,
1004
+ width: estimateW(msg.label) + 12,
1005
+ height: 18,
1006
+ rx: 6,
1007
+ fill: t.canvas,
1008
+ stroke: selectedHere ? INDIGO : t.cardBorder,
1009
+ strokeWidth: selectedHere ? 1.25 : 1
1010
+ }
1011
+ ),
1012
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: labelX, y: y - 5, textAnchor: "middle", fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
1013
+ ] }, msg.id);
1014
+ }),
1015
+ actors.map((name) => {
1016
+ const x = actorX(name);
1017
+ const w = colW - 24;
1018
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { children: [
1019
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1020
+ "rect",
1021
+ {
1022
+ x: x - w / 2,
1023
+ y: HEADER_PAD,
1024
+ width: w,
1025
+ height: HEADER_H,
1026
+ rx: 12,
1027
+ fill: t.actorFill,
1028
+ stroke: t.actorStroke,
1029
+ strokeWidth: 1.25,
1030
+ filter: "url(#seqShadow)"
1031
+ }
1032
+ ),
1033
+ editingId === name ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("foreignObject", { x: x - w / 2 + 8, y: HEADER_PAD + 16, width: w - 16, height: 32, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1034
+ "input",
1035
+ {
1036
+ autoFocus: true,
1037
+ defaultValue: name,
1038
+ onBlur: (e) => {
1039
+ renameActor(name, e.currentTarget.value.trim());
1040
+ setEditingId(null);
1041
+ },
1042
+ onKeyDown: (e) => {
1043
+ if (e.key === "Enter") {
1044
+ renameActor(name, e.target.value.trim());
1045
+ setEditingId(null);
1046
+ }
1047
+ if (e.key === "Escape") setEditingId(null);
1048
+ },
1049
+ style: {
1050
+ width: "100%",
1051
+ height: "100%",
1052
+ border: "none",
1053
+ borderRadius: 6,
1054
+ outline: `2px solid ${INDIGO}`,
1055
+ textAlign: "center",
1056
+ fontSize: 13,
1057
+ fontWeight: 600,
1058
+ background: t.inputBg,
1059
+ color: t.inputText,
1060
+ boxSizing: "border-box",
1061
+ padding: "0 6px",
1062
+ fontFamily: "inherit"
1063
+ }
1064
+ }
1065
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1066
+ "text",
1067
+ {
1068
+ x,
1069
+ y: HEADER_PAD + HEADER_H / 2 + 4,
1070
+ textAnchor: "middle",
1071
+ fontSize: 13,
1072
+ fontWeight: 700,
1073
+ fill: t.actorText,
1074
+ style: STYLE_SEQ_ACTOR_TEXT,
1075
+ onDoubleClick: () => setEditingId(name),
1076
+ children: name
1077
+ }
1078
+ ),
1079
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1080
+ "circle",
1081
+ {
1082
+ cx: x + w / 2 - 12,
1083
+ cy: HEADER_PAD + 14,
1084
+ r: 9,
1085
+ fill: "transparent",
1086
+ style: STYLE_SEQ_REMOVE_BTN,
1087
+ onClick: () => removeActor(name),
1088
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: "Remove actor" })
1089
+ }
1090
+ ),
1091
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1092
+ "text",
1093
+ {
1094
+ x: x + w / 2 - 12,
1095
+ y: HEADER_PAD + 18,
1096
+ textAnchor: "middle",
1097
+ fontSize: 12,
1098
+ fill: t.textMuted,
1099
+ style: STYLE_SEQ_REMOVE_ICON,
1100
+ children: "\xD7"
1101
+ }
1102
+ )
1103
+ ] }, `hdr-${name}`);
1104
+ })
1105
+ ]
1106
+ }
1107
+ );
1108
+ }
843
1109
 
844
1110
  // src/ui/hooks/useEditorTheme.ts
845
- var import_react5 = require("react");
1111
+ var import_react6 = require("react");
846
1112
 
847
1113
  // src/ui/hooks/useSystemTheme.ts
848
- var import_react4 = require("react");
1114
+ var import_react5 = require("react");
849
1115
  function useIsDark(theme) {
850
- const [sysDark, setSysDark] = (0, import_react4.useState)(
1116
+ const [sysDark, setSysDark] = (0, import_react5.useState)(
851
1117
  () => typeof window !== "undefined" ? window.matchMedia("(prefers-color-scheme: dark)").matches : false
852
1118
  );
853
- (0, import_react4.useEffect)(() => {
1119
+ (0, import_react5.useEffect)(() => {
854
1120
  if (theme !== "auto" || typeof window === "undefined") return;
855
1121
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
856
1122
  const handler = (e) => setSysDark(e.matches);
@@ -860,10 +1126,10 @@ function useIsDark(theme) {
860
1126
  return theme === "dark" || theme === "auto" && sysDark;
861
1127
  }
862
1128
  function useIsCoarsePointer() {
863
- const [coarse, setCoarse] = (0, import_react4.useState)(
1129
+ const [coarse, setCoarse] = (0, import_react5.useState)(
864
1130
  () => typeof window !== "undefined" ? window.matchMedia("(pointer: coarse)").matches : false
865
1131
  );
866
- (0, import_react4.useEffect)(() => {
1132
+ (0, import_react5.useEffect)(() => {
867
1133
  if (typeof window === "undefined") return;
868
1134
  const mq = window.matchMedia("(pointer: coarse)");
869
1135
  const handler = (e) => setCoarse(e.matches);
@@ -873,10 +1139,10 @@ function useIsCoarsePointer() {
873
1139
  return coarse;
874
1140
  }
875
1141
  function usePrefersReducedMotion() {
876
- const [reduced, setReduced] = (0, import_react4.useState)(
1142
+ const [reduced, setReduced] = (0, import_react5.useState)(
877
1143
  () => typeof window !== "undefined" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false
878
1144
  );
879
- (0, import_react4.useEffect)(() => {
1145
+ (0, import_react5.useEffect)(() => {
880
1146
  if (typeof window === "undefined") return;
881
1147
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
882
1148
  const handler = (e) => setReduced(e.matches);
@@ -889,7 +1155,7 @@ function usePrefersReducedMotion() {
889
1155
  // src/ui/hooks/useEditorTheme.ts
890
1156
  function useEditorTheme(theme, overrides, palettes) {
891
1157
  const isDark = useIsDark(theme);
892
- const t = (0, import_react5.useMemo)(
1158
+ const t = (0, import_react6.useMemo)(
893
1159
  () => ({ ...isDark ? palettes.dark : palettes.light, ...overrides ?? {} }),
894
1160
  // palettes is a stable module-level constant in every caller, so it is
895
1161
  // deliberately omitted from the dep array to keep the memo key tight.
@@ -900,7 +1166,7 @@ function useEditorTheme(theme, overrides, palettes) {
900
1166
  }
901
1167
 
902
1168
  // src/ui/hooks/useExporters.ts
903
- var import_react6 = require("react");
1169
+ var import_react7 = require("react");
904
1170
 
905
1171
  // src/exporters/mermaid.ts
906
1172
  var SHAPE_OPEN = {
@@ -1287,7 +1553,7 @@ async function toPNG(model) {
1287
1553
 
1288
1554
  // src/ui/hooks/useExporters.ts
1289
1555
  function useExporters(model, onExport, filename = "diagram") {
1290
- return (0, import_react6.useCallback)(async (format) => {
1556
+ return (0, import_react7.useCallback)(async (format) => {
1291
1557
  let content;
1292
1558
  switch (format) {
1293
1559
  case "mermaid":
@@ -1322,7 +1588,7 @@ function useExporters(model, onExport, filename = "diagram") {
1322
1588
  }
1323
1589
 
1324
1590
  // src/ui/hooks/useImporter.ts
1325
- var import_react7 = require("react");
1591
+ var import_react8 = require("react");
1326
1592
 
1327
1593
  // src/core/model.ts
1328
1594
  var Model = class _Model {
@@ -1617,7 +1883,7 @@ function fromJSON(json) {
1617
1883
  // src/ui/hooks/useImporter.ts
1618
1884
  function useImporter(applyAndPush, options = {}) {
1619
1885
  const { expectedType, transform } = options;
1620
- return (0, import_react7.useCallback)((text) => {
1886
+ return (0, import_react8.useCallback)((text) => {
1621
1887
  try {
1622
1888
  const parsed = text.trim().startsWith("{") ? fromJSON(text).toJSON() : fromMermaid(text).toJSON();
1623
1889
  if (expectedType && parsed.type !== expectedType) {
@@ -1727,10 +1993,33 @@ function cloneModel(m) {
1727
1993
  };
1728
1994
  }
1729
1995
 
1996
+ // src/ui/hooks/useEditorKeyboard.ts
1997
+ var import_react9 = require("react");
1998
+ var isInput = (e) => {
1999
+ const tgt = e.target;
2000
+ return !!(tgt && (tgt.tagName === "INPUT" || tgt.tagName === "TEXTAREA" || tgt.isContentEditable));
2001
+ };
2002
+ function useEditorKeyboard(commands, deps) {
2003
+ (0, import_react9.useEffect)(() => {
2004
+ const onKey = (e) => {
2005
+ if (isInput(e)) return;
2006
+ for (const cmd of commands) {
2007
+ if (cmd.match(e)) {
2008
+ const handled = cmd.run(e);
2009
+ if (handled) e.preventDefault();
2010
+ return;
2011
+ }
2012
+ }
2013
+ };
2014
+ window.addEventListener("keydown", onKey);
2015
+ return () => window.removeEventListener("keydown", onKey);
2016
+ }, deps);
2017
+ }
2018
+
1730
2019
  // src/ui/SequenceEditor.tsx
1731
- var import_jsx_runtime4 = require("react/jsx-runtime");
1732
- var INDIGO = "#4f46e5";
1733
- var INDIGO_SOFT = "#eef2ff";
2020
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2021
+ var INDIGO2 = "#4f46e5";
2022
+ var INDIGO_SOFT2 = "#eef2ff";
1734
2023
  var lightTheme2 = {
1735
2024
  canvas: "#fafbfc",
1736
2025
  dot: "#dbe3ee",
@@ -1773,11 +2062,11 @@ var darkTheme2 = {
1773
2062
  actorStroke: "rgba(99,102,241,0.45)",
1774
2063
  actorText: "#a5b4fc"
1775
2064
  };
1776
- var HEADER_H = 64;
1777
- var HEADER_PAD = 24;
2065
+ var HEADER_H2 = 64;
2066
+ var HEADER_PAD2 = 24;
1778
2067
  var COL_MIN = 160;
1779
- var ROW_H = 64;
1780
- var SIDE_PAD = 40;
2068
+ var ROW_H2 = 64;
2069
+ var SIDE_PAD2 = 40;
1781
2070
  var DRAG_THRESHOLD = 5;
1782
2071
  function ensureSequenceModel(m) {
1783
2072
  if (m && m.type === "sequence") {
@@ -1795,49 +2084,49 @@ function SequenceEditor({
1795
2084
  theme = "auto",
1796
2085
  themeOverrides
1797
2086
  }) {
1798
- const [model, setModel] = (0, import_react8.useState)(() => ensureSequenceModel(initialModel));
1799
- const [selected, setSelected] = (0, import_react8.useState)(null);
1800
- const [drag, setDrag] = (0, import_react8.useState)(null);
1801
- const [editingId, setEditingId] = (0, import_react8.useState)(null);
1802
- const [editLabel, setEditLabel] = (0, import_react8.useState)("");
1803
- const historyRef = (0, import_react8.useRef)([ensureSequenceModel(initialModel)]);
1804
- const historyIdxRef = (0, import_react8.useRef)(0);
1805
- const svgRef = (0, import_react8.useRef)(null);
2087
+ const [model, setModel] = (0, import_react10.useState)(() => ensureSequenceModel(initialModel));
2088
+ const [selected, setSelected] = (0, import_react10.useState)(null);
2089
+ const [drag, setDrag] = (0, import_react10.useState)(null);
2090
+ const [editingId, setEditingId] = (0, import_react10.useState)(null);
2091
+ const [editLabel, setEditLabel] = (0, import_react10.useState)("");
2092
+ const historyRef = (0, import_react10.useRef)([ensureSequenceModel(initialModel)]);
2093
+ const historyIdxRef = (0, import_react10.useRef)(0);
2094
+ const svgRef = (0, import_react10.useRef)(null);
1806
2095
  const { t, isDark } = useEditorTheme(theme, themeOverrides, { light: lightTheme2, dark: darkTheme2 });
1807
2096
  const actors = model.actors ?? [];
1808
2097
  const messages = model.messages ?? [];
1809
- const colW = (0, import_react8.useMemo)(() => {
2098
+ const colW = (0, import_react10.useMemo)(() => {
1810
2099
  const longest = actors.reduce((m, a) => Math.max(m, a.length), 6);
1811
2100
  return Math.max(COL_MIN, longest * 9 + 40);
1812
2101
  }, [actors]);
1813
- const totalW = SIDE_PAD * 2 + Math.max(1, actors.length) * colW;
1814
- const totalH = HEADER_PAD + HEADER_H + 32 + messages.length * ROW_H + 48;
2102
+ const totalW = SIDE_PAD2 * 2 + Math.max(1, actors.length) * colW;
2103
+ const totalH = HEADER_PAD2 + HEADER_H2 + 32 + messages.length * ROW_H2 + 48;
1815
2104
  const actorX = (name) => {
1816
2105
  const idx = actors.indexOf(name);
1817
- if (idx < 0) return SIDE_PAD + colW / 2;
1818
- return SIDE_PAD + idx * colW + colW / 2;
2106
+ if (idx < 0) return SIDE_PAD2 + colW / 2;
2107
+ return SIDE_PAD2 + idx * colW + colW / 2;
1819
2108
  };
1820
- const msgY = (idx) => HEADER_PAD + HEADER_H + 40 + idx * ROW_H;
1821
- const pushHistory = (0, import_react8.useCallback)((m) => {
2109
+ const msgY = (idx) => HEADER_PAD2 + HEADER_H2 + 40 + idx * ROW_H2;
2110
+ const pushHistory = (0, import_react10.useCallback)((m) => {
1822
2111
  const stack = historyRef.current.slice(0, historyIdxRef.current + 1);
1823
2112
  stack.push(m);
1824
2113
  if (stack.length > 80) stack.shift();
1825
2114
  historyRef.current = stack;
1826
2115
  historyIdxRef.current = stack.length - 1;
1827
2116
  }, []);
1828
- const applyAndPush = (0, import_react8.useCallback)((m) => {
2117
+ const applyAndPush = (0, import_react10.useCallback)((m) => {
1829
2118
  setModel(m);
1830
2119
  onChange?.(m);
1831
2120
  pushHistory(m);
1832
2121
  }, [onChange, pushHistory]);
1833
- const undo = (0, import_react8.useCallback)(() => {
2122
+ const undo = (0, import_react10.useCallback)(() => {
1834
2123
  if (historyIdxRef.current <= 0) return;
1835
2124
  historyIdxRef.current--;
1836
2125
  const m = historyRef.current[historyIdxRef.current];
1837
2126
  setModel(m);
1838
2127
  onChange?.(m);
1839
2128
  }, [onChange]);
1840
- const redo = (0, import_react8.useCallback)(() => {
2129
+ const redo = (0, import_react10.useCallback)(() => {
1841
2130
  if (historyIdxRef.current >= historyRef.current.length - 1) return;
1842
2131
  historyIdxRef.current++;
1843
2132
  const m = historyRef.current[historyIdxRef.current];
@@ -1895,7 +2184,7 @@ function SequenceEditor({
1895
2184
  applyAndPush({ ...model, messages: messages.filter((m) => m.id !== id) });
1896
2185
  if (selected === id) setSelected(null);
1897
2186
  };
1898
- const reorderMessage = (0, import_react8.useCallback)((id, toIdx) => {
2187
+ const reorderMessage = (0, import_react10.useCallback)((id, toIdx) => {
1899
2188
  const fromIdx = messages.findIndex((m) => m.id === id);
1900
2189
  if (fromIdx < 0 || toIdx === fromIdx) return;
1901
2190
  const next = messages.slice();
@@ -1903,34 +2192,26 @@ function SequenceEditor({
1903
2192
  next.splice(toIdx, 0, moved);
1904
2193
  applyAndPush({ ...model, messages: next });
1905
2194
  }, [messages, model, applyAndPush]);
1906
- (0, import_react8.useEffect)(() => {
1907
- const onKey = (e) => {
1908
- const tgt = e.target;
1909
- if (tgt && (tgt.tagName === "INPUT" || tgt.tagName === "TEXTAREA" || tgt.isContentEditable)) return;
1910
- const ctrl = e.ctrlKey || e.metaKey;
1911
- if (ctrl && e.key === "z") {
1912
- e.preventDefault();
1913
- undo();
1914
- return;
1915
- }
1916
- if (ctrl && (e.key === "y" || e.shiftKey && e.key === "z")) {
1917
- e.preventDefault();
1918
- redo();
1919
- return;
1920
- }
1921
- if (e.key === "Escape") {
1922
- setSelected(null);
1923
- setEditingId(null);
1924
- return;
1925
- }
1926
- if ((e.key === "Delete" || e.key === "Backspace") && selected) {
1927
- e.preventDefault();
1928
- removeMessage(selected);
1929
- }
1930
- };
1931
- window.addEventListener("keydown", onKey);
1932
- return () => window.removeEventListener("keydown", onKey);
1933
- }, [undo, redo, selected]);
2195
+ const keyCommands = [
2196
+ { match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z", run: () => {
2197
+ undo();
2198
+ return true;
2199
+ } },
2200
+ { match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"), run: () => {
2201
+ redo();
2202
+ return true;
2203
+ } },
2204
+ { match: (e) => e.key === "Escape", run: () => {
2205
+ setSelected(null);
2206
+ setEditingId(null);
2207
+ return true;
2208
+ } },
2209
+ { match: (e) => (e.key === "Delete" || e.key === "Backspace") && !!selected, run: () => {
2210
+ removeMessage(selected);
2211
+ return true;
2212
+ } }
2213
+ ];
2214
+ useEditorKeyboard(keyCommands, [undo, redo, selected]);
1934
2215
  const handleExport = useExporters(model, onExport, "sequence");
1935
2216
  const handleImport = useImporter(applyAndPush, {
1936
2217
  expectedType: "sequence",
@@ -1945,9 +2226,9 @@ function SequenceEditor({
1945
2226
  setSelected(id);
1946
2227
  setDrag({ id, startY: e.clientY, originalIdx: idx, targetIdx: idx, active: false });
1947
2228
  };
1948
- (0, import_react8.useEffect)(() => {
2229
+ (0, import_react10.useEffect)(() => {
1949
2230
  if (!drag) return;
1950
- const baseY = HEADER_PAD + HEADER_H + 40;
2231
+ const baseY = HEADER_PAD2 + HEADER_H2 + 40;
1951
2232
  const onMove = (ev) => {
1952
2233
  const dy = ev.clientY - drag.startY;
1953
2234
  if (!drag.active && Math.abs(dy) < DRAG_THRESHOLD) return;
@@ -1955,7 +2236,7 @@ function SequenceEditor({
1955
2236
  if (!svg) return;
1956
2237
  const rect = svg.getBoundingClientRect();
1957
2238
  const yInSvg = ev.clientY - rect.top;
1958
- const raw = Math.floor((yInSvg - baseY + ROW_H / 2) / ROW_H);
2239
+ const raw = Math.floor((yInSvg - baseY + ROW_H2 / 2) / ROW_H2);
1959
2240
  const next = Math.max(0, Math.min(messages.length - 1, raw));
1960
2241
  if (next === drag.targetIdx && drag.active) return;
1961
2242
  setDrag({ ...drag, active: true, targetIdx: next });
@@ -1973,17 +2254,8 @@ function SequenceEditor({
1973
2254
  window.removeEventListener("mouseup", onUp);
1974
2255
  };
1975
2256
  }, [drag, messages.length, reorderMessage]);
1976
- const visualMessages = (0, import_react8.useMemo)(() => {
1977
- if (!drag?.active) return messages;
1978
- const idx = messages.findIndex((m) => m.id === drag.id);
1979
- if (idx < 0) return messages;
1980
- const next = messages.slice();
1981
- const [moved] = next.splice(idx, 1);
1982
- next.splice(drag.targetIdx, 0, moved);
1983
- return next;
1984
- }, [messages, drag]);
1985
2257
  const selectedMsg = selected ? messages.find((m) => m.id === selected) : null;
1986
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
2258
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
1987
2259
  display: "flex",
1988
2260
  flexDirection: "column",
1989
2261
  height,
@@ -1991,8 +2263,8 @@ function SequenceEditor({
1991
2263
  fontFamily: "ui-sans-serif,system-ui,sans-serif",
1992
2264
  background: t.ctrlsBg
1993
2265
  }, children: [
1994
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
1995
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
2266
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
2267
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
1996
2268
  display: "flex",
1997
2269
  gap: 8,
1998
2270
  padding: "7px 14px",
@@ -2001,12 +2273,12 @@ function SequenceEditor({
2001
2273
  alignItems: "center",
2002
2274
  flexWrap: "wrap"
2003
2275
  }, children: [
2004
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: addActor, style: primaryBtn(), children: "+ Actor" }),
2005
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: addMessage, style: primaryBtn(), children: "+ Message" }),
2006
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: 1, height: 18, background: t.ctrlsBorder, margin: "0 4px" } }),
2007
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: undo, style: ghostBtn2(t), title: "Undo (Ctrl+Z)", children: "\u21B6" }),
2008
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: redo, style: ghostBtn2(t), title: "Redo (Ctrl+Y)", children: "\u21B7" }),
2009
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
2276
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: addActor, style: primaryBtn(), children: "+ Actor" }),
2277
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: addMessage, style: primaryBtn(), children: "+ Message" }),
2278
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { width: 1, height: 18, background: t.ctrlsBorder, margin: "0 4px" } }),
2279
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: undo, style: ghostBtn2(t), title: "Undo (Ctrl+Z)", children: "\u21B6" }),
2280
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: redo, style: ghostBtn2(t), title: "Redo (Ctrl+Y)", children: "\u21B7" }),
2281
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
2010
2282
  actors.length,
2011
2283
  " actor",
2012
2284
  actors.length === 1 ? "" : "s",
@@ -2017,215 +2289,31 @@ function SequenceEditor({
2017
2289
  " \xB7 drag a row to reorder"
2018
2290
  ] })
2019
2291
  ] }),
2020
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
2021
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { flex: 1, overflow: "auto", background: t.canvas, position: "relative" }, children: actors.length === 0 && messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
2022
- position: "absolute",
2023
- inset: 0,
2024
- display: "flex",
2025
- flexDirection: "column",
2026
- alignItems: "center",
2027
- justifyContent: "center",
2028
- gap: 10,
2029
- color: t.textMuted,
2030
- pointerEvents: "none"
2031
- }, children: [
2032
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 36, opacity: 0.15, color: t.textPrimary }, children: "\u2194" }),
2033
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
2034
- "Click ",
2035
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Actor" }),
2036
- " then ",
2037
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Message" }),
2038
- " to start"
2039
- ] })
2040
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2041
- "svg",
2292
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
2293
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { flex: 1, overflow: "auto", background: t.canvas, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2294
+ SequenceCanvas,
2042
2295
  {
2043
- ref: svgRef,
2044
- width: totalW,
2045
- height: totalH,
2046
- style: { display: "block", cursor: drag?.active ? "grabbing" : "default", userSelect: "none" },
2047
- children: [
2048
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("defs", { children: [
2049
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("pattern", { id: "seqdots", x: "0", y: "0", width: "24", height: "24", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: 12, cy: 12, r: 1.1, fill: t.dot }) }),
2050
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("filter", { id: "seqShadow", x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("feDropShadow", { dx: 0, dy: 3, stdDeviation: 5, floodColor: isDark ? "rgba(0,0,0,0.5)" : "rgba(15,23,42,0.09)" }) }),
2051
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("marker", { id: "seqArrow", markerWidth: 9, markerHeight: 7, refX: 8.5, refY: 3.5, orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: t.arrow }) })
2052
- ] }),
2053
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: totalW, height: totalH, fill: "url(#seqdots)" }),
2054
- actors.map((name) => {
2055
- const x = actorX(name);
2056
- const top = HEADER_PAD + HEADER_H;
2057
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2058
- "line",
2059
- {
2060
- x1: x,
2061
- x2: x,
2062
- y1: top + 4,
2063
- y2: totalH - 24,
2064
- stroke: t.lifeline,
2065
- strokeWidth: 1.25,
2066
- strokeDasharray: "5 5"
2067
- },
2068
- `life-${name}`
2069
- );
2070
- }),
2071
- visualMessages.map((msg, idx) => {
2072
- const y = msgY(idx);
2073
- const fromX = actorX(msg.from);
2074
- const toX = actorX(msg.to);
2075
- const selectedHere = selected === msg.id;
2076
- const isDragging = drag?.active && drag.id === msg.id;
2077
- const isSelf = msg.from === msg.to;
2078
- const stroke = selectedHere ? INDIGO : t.arrow;
2079
- const dash = msg.style === "dashed" ? "6,4" : void 0;
2080
- const cursor = drag?.active ? "grabbing" : "grab";
2081
- const groupOpacity = isDragging ? 0.85 : 1;
2082
- if (isSelf) {
2083
- const startX = fromX;
2084
- const loopW = 36;
2085
- const loopY = y - 6;
2086
- const d = `M ${startX} ${loopY} C ${startX + loopW} ${loopY}, ${startX + loopW} ${loopY + 24}, ${startX} ${loopY + 24}`;
2087
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: { cursor, opacity: groupOpacity }, children: [
2088
- (selectedHere || isDragging) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2089
- "rect",
2090
- {
2091
- x: SIDE_PAD - 8,
2092
- y: y - 22,
2093
- width: totalW - (SIDE_PAD - 8) * 2,
2094
- height: ROW_H - 12,
2095
- rx: 10,
2096
- fill: INDIGO_SOFT,
2097
- opacity: isDark ? 0.18 : 0.6
2098
- }
2099
- ),
2100
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d, fill: "none", stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
2101
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: startX + loopW + 8, y: loopY + 16, fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
2102
- ] }, msg.id);
2103
- }
2104
- const labelX = (fromX + toX) / 2;
2105
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: { cursor, opacity: groupOpacity }, children: [
2106
- (selectedHere || isDragging) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2107
- "rect",
2108
- {
2109
- x: SIDE_PAD - 8,
2110
- y: y - 22,
2111
- width: totalW - (SIDE_PAD - 8) * 2,
2112
- height: ROW_H - 12,
2113
- rx: 10,
2114
- fill: INDIGO_SOFT,
2115
- opacity: isDark ? 0.18 : 0.6
2116
- }
2117
- ),
2118
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: fromX, y1: y, x2: toX, y2: y, stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
2119
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2120
- "rect",
2121
- {
2122
- x: labelX - estimateW(msg.label) / 2 - 6,
2123
- y: y - 18,
2124
- width: estimateW(msg.label) + 12,
2125
- height: 18,
2126
- rx: 6,
2127
- fill: t.canvas,
2128
- stroke: selectedHere ? INDIGO : t.cardBorder,
2129
- strokeWidth: selectedHere ? 1.25 : 1
2130
- }
2131
- ),
2132
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: labelX, y: y - 5, textAnchor: "middle", fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
2133
- ] }, msg.id);
2134
- }),
2135
- actors.map((name) => {
2136
- const x = actorX(name);
2137
- const w = colW - 24;
2138
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { children: [
2139
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2140
- "rect",
2141
- {
2142
- x: x - w / 2,
2143
- y: HEADER_PAD,
2144
- width: w,
2145
- height: HEADER_H,
2146
- rx: 12,
2147
- fill: t.actorFill,
2148
- stroke: t.actorStroke,
2149
- strokeWidth: 1.25,
2150
- filter: "url(#seqShadow)"
2151
- }
2152
- ),
2153
- editingId === name ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("foreignObject", { x: x - w / 2 + 8, y: HEADER_PAD + 16, width: w - 16, height: 32, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2154
- "input",
2155
- {
2156
- autoFocus: true,
2157
- defaultValue: name,
2158
- onBlur: (e) => {
2159
- renameActor(name, e.currentTarget.value.trim());
2160
- setEditingId(null);
2161
- },
2162
- onKeyDown: (e) => {
2163
- if (e.key === "Enter") {
2164
- renameActor(name, e.target.value.trim());
2165
- setEditingId(null);
2166
- }
2167
- if (e.key === "Escape") setEditingId(null);
2168
- },
2169
- style: {
2170
- width: "100%",
2171
- height: "100%",
2172
- border: "none",
2173
- borderRadius: 6,
2174
- outline: `2px solid ${INDIGO}`,
2175
- textAlign: "center",
2176
- fontSize: 13,
2177
- fontWeight: 600,
2178
- background: t.inputBg,
2179
- color: t.inputText,
2180
- boxSizing: "border-box",
2181
- padding: "0 6px",
2182
- fontFamily: "inherit"
2183
- }
2184
- }
2185
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2186
- "text",
2187
- {
2188
- x,
2189
- y: HEADER_PAD + HEADER_H / 2 + 4,
2190
- textAnchor: "middle",
2191
- fontSize: 13,
2192
- fontWeight: 700,
2193
- fill: t.actorText,
2194
- style: { cursor: "pointer", userSelect: "none" },
2195
- onDoubleClick: () => setEditingId(name),
2196
- children: name
2197
- }
2198
- ),
2199
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2200
- "circle",
2201
- {
2202
- cx: x + w / 2 - 12,
2203
- cy: HEADER_PAD + 14,
2204
- r: 9,
2205
- fill: "transparent",
2206
- style: { cursor: "pointer" },
2207
- onClick: () => removeActor(name),
2208
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: "Remove actor" })
2209
- }
2210
- ),
2211
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2212
- "text",
2213
- {
2214
- x: x + w / 2 - 12,
2215
- y: HEADER_PAD + 18,
2216
- textAnchor: "middle",
2217
- fontSize: 12,
2218
- fill: t.textMuted,
2219
- style: { pointerEvents: "none", userSelect: "none" },
2220
- children: "\xD7"
2221
- }
2222
- )
2223
- ] }, `hdr-${name}`);
2224
- })
2225
- ]
2296
+ model,
2297
+ actors,
2298
+ messages,
2299
+ t,
2300
+ isDark,
2301
+ colW,
2302
+ totalW,
2303
+ totalH,
2304
+ actorX,
2305
+ msgY,
2306
+ selected,
2307
+ editingId,
2308
+ setEditingId,
2309
+ drag,
2310
+ onRowMouseDown,
2311
+ renameActor,
2312
+ removeActor,
2313
+ svgRef
2226
2314
  }
2227
2315
  ) }),
2228
- selectedMsg && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
2316
+ selectedMsg && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
2229
2317
  width: 280,
2230
2318
  flexShrink: 0,
2231
2319
  background: t.panelBg,
@@ -2233,9 +2321,9 @@ function SequenceEditor({
2233
2321
  padding: "14px 16px",
2234
2322
  overflowY: "auto"
2235
2323
  }, children: [
2236
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.7, marginBottom: 10 }, children: "Message" }),
2237
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Label, { t, children: "Label" }),
2238
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2324
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.7, marginBottom: 10 }, children: "Message" }),
2325
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label, { t, children: "Label" }),
2326
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2239
2327
  "input",
2240
2328
  {
2241
2329
  value: editLabel || selectedMsg.label,
@@ -2251,21 +2339,21 @@ function SequenceEditor({
2251
2339
  style: input(t)
2252
2340
  }
2253
2341
  ),
2254
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Label, { t, children: "From" }),
2255
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { value: selectedMsg.from, onChange: (e) => updateMessage(selectedMsg.id, { from: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: a, children: a }, a)) }),
2256
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Label, { t, children: "To" }),
2257
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { value: selectedMsg.to, onChange: (e) => updateMessage(selectedMsg.id, { to: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: a, children: a }, a)) }),
2258
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Label, { t, children: "Style" }),
2259
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { display: "flex", gap: 6 }, children: ["solid", "dashed"].map((s2) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2342
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label, { t, children: "From" }),
2343
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("select", { value: selectedMsg.from, onChange: (e) => updateMessage(selectedMsg.id, { from: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: a, children: a }, a)) }),
2344
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label, { t, children: "To" }),
2345
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("select", { value: selectedMsg.to, onChange: (e) => updateMessage(selectedMsg.id, { to: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: a, children: a }, a)) }),
2346
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label, { t, children: "Style" }),
2347
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { display: "flex", gap: 6 }, children: ["solid", "dashed"].map((s2) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2260
2348
  "button",
2261
2349
  {
2262
2350
  onClick: () => updateMessage(selectedMsg.id, { style: s2 }),
2263
2351
  style: {
2264
2352
  flex: 1,
2265
2353
  padding: "6px 10px",
2266
- border: `1.5px solid ${selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO : t.inputBorder}`,
2267
- background: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO_SOFT : t.inputBg,
2268
- color: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO : t.textPrimary,
2354
+ border: `1.5px solid ${selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.inputBorder}`,
2355
+ background: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO_SOFT2 : t.inputBg,
2356
+ color: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.textPrimary,
2269
2357
  borderRadius: 8,
2270
2358
  fontSize: 12,
2271
2359
  fontWeight: 600,
@@ -2276,8 +2364,8 @@ function SequenceEditor({
2276
2364
  },
2277
2365
  s2
2278
2366
  )) }),
2279
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: 14 } }),
2280
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2367
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { height: 14 } }),
2368
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2281
2369
  "button",
2282
2370
  {
2283
2371
  onClick: () => removeMessage(selectedMsg.id),
@@ -2287,7 +2375,7 @@ function SequenceEditor({
2287
2375
  )
2288
2376
  ] })
2289
2377
  ] }),
2290
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
2378
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
2291
2379
  padding: "4px 14px",
2292
2380
  fontSize: 11,
2293
2381
  color: t.textMuted,
@@ -2296,25 +2384,22 @@ function SequenceEditor({
2296
2384
  display: "flex",
2297
2385
  gap: 16
2298
2386
  }, children: [
2299
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
2387
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
2300
2388
  actors.length,
2301
2389
  " actors"
2302
2390
  ] }),
2303
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
2391
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
2304
2392
  messages.length,
2305
2393
  " messages"
2306
2394
  ] }),
2307
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { marginLeft: "auto" }, children: "double-click actor to rename \xB7 drag a row to reorder" })
2395
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { marginLeft: "auto" }, children: "double-click actor to rename \xB7 drag a row to reorder" })
2308
2396
  ] })
2309
2397
  ] });
2310
2398
  }
2311
- function estimateW(text, pxPerChar = 7) {
2312
- return text.length * pxPerChar;
2313
- }
2314
2399
  function primaryBtn() {
2315
2400
  return {
2316
2401
  padding: "6px 12px",
2317
- background: INDIGO,
2402
+ background: INDIGO2,
2318
2403
  color: "#fff",
2319
2404
  border: "none",
2320
2405
  borderRadius: 8,
@@ -2353,180 +2438,37 @@ function input(t) {
2353
2438
  };
2354
2439
  }
2355
2440
  function Label({ t, children }) {
2356
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.6, marginBottom: 4 }, children });
2441
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.6, marginBottom: 4 }, children });
2357
2442
  }
2358
2443
 
2359
- // src/ui/Minimap.tsx
2360
- var import_react9 = require("react");
2361
- var import_jsx_runtime5 = require("react/jsx-runtime");
2362
- var W = 168;
2363
- var H = 112;
2364
- var PAD = 18;
2365
- function Minimap({
2444
+ // src/ui/NodeNavigator.tsx
2445
+ var import_react11 = require("react");
2446
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2447
+ function NodeNavigator({
2366
2448
  model,
2367
- viewportW,
2368
- viewportH,
2369
- transform,
2370
- measureNode,
2371
- onCenterOn,
2449
+ selected,
2450
+ variant,
2372
2451
  isDark,
2373
- accentColor
2452
+ t,
2453
+ acc,
2454
+ open,
2455
+ onToggle,
2456
+ onSelect
2374
2457
  }) {
2375
- const dragRef = (0, import_react9.useRef)(null);
2376
- const boxes = model.nodes.map((n) => {
2377
- const { w, h } = measureNode(n);
2378
- return { id: n.id, x: n.x ?? 0, y: n.y ?? 0, w, h };
2379
- });
2380
- if (boxes.length === 0) return null;
2381
- const vx = -transform.x / transform.scale;
2382
- const vy = -transform.y / transform.scale;
2383
- const vw = viewportW / transform.scale;
2384
- const vh = viewportH / transform.scale;
2385
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
2386
- for (const b of boxes) {
2387
- minX = Math.min(minX, b.x);
2388
- minY = Math.min(minY, b.y);
2389
- maxX = Math.max(maxX, b.x + b.w);
2390
- maxY = Math.max(maxY, b.y + b.h);
2391
- }
2392
- minX = Math.min(minX, vx);
2393
- minY = Math.min(minY, vy);
2394
- maxX = Math.max(maxX, vx + vw);
2395
- maxY = Math.max(maxY, vy + vh);
2396
- const contentW = Math.max(1, maxX - minX);
2397
- const contentH = Math.max(1, maxY - minY);
2398
- const scale = Math.min((W - PAD * 2) / contentW, (H - PAD * 2) / contentH);
2399
- const offsetX = (W - contentW * scale) / 2 - minX * scale;
2400
- const offsetY = (H - contentH * scale) / 2 - minY * scale;
2401
- const project = (x, y) => ({
2402
- x: offsetX + x * scale,
2403
- y: offsetY + y * scale
2404
- });
2405
- const unproject = (mx, my) => ({
2406
- x: (mx - offsetX) / scale,
2407
- y: (my - offsetY) / scale
2408
- });
2409
- const panTo = (0, import_react9.useCallback)((e) => {
2410
- const rect = e.currentTarget.getBoundingClientRect();
2411
- const mx = e.clientX - rect.left;
2412
- const my = e.clientY - rect.top;
2413
- const { x, y } = unproject(mx, my);
2414
- onCenterOn(x, y);
2415
- }, [onCenterOn, scale, offsetX, offsetY]);
2416
- const onMouseDown = (e) => {
2417
- e.stopPropagation();
2418
- dragRef.current = { active: true };
2419
- panTo(e);
2420
- };
2421
- const onMouseMove = (e) => {
2422
- if (!dragRef.current?.active) return;
2423
- panTo(e);
2424
- };
2425
- const onMouseUp = () => {
2426
- dragRef.current = null;
2427
- };
2428
- const bg = isDark ? "rgba(15,23,42,0.92)" : "rgba(255,255,255,0.94)";
2429
- const border = isDark ? "#334155" : "#e2e8f0";
2430
- const nodeFill = isDark ? "#475569" : "#cbd5e1";
2431
- const viewStroke = accentColor;
2432
- const viewFill = `${accentColor}22`;
2433
- const vp1 = project(vx, vy);
2434
- const vp2 = project(vx + vw, vy + vh);
2435
- const vpRect = {
2436
- x: Math.max(0, Math.min(W, vp1.x)),
2437
- y: Math.max(0, Math.min(H, vp1.y)),
2438
- w: Math.max(2, Math.min(W, vp2.x) - Math.max(0, vp1.x)),
2439
- h: Math.max(2, Math.min(H, vp2.y) - Math.max(0, vp1.y))
2440
- };
2441
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2442
- "div",
2443
- {
2444
- style: {
2445
- position: "absolute",
2446
- bottom: 14,
2447
- right: 14,
2448
- background: bg,
2449
- border: `1px solid ${border}`,
2450
- borderRadius: 10,
2451
- padding: 6,
2452
- boxShadow: isDark ? "0 8px 20px rgba(0,0,0,0.45)" : "0 6px 18px rgba(15,23,42,0.08)",
2453
- backdropFilter: "blur(6px)"
2454
- },
2455
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2456
- "svg",
2457
- {
2458
- width: W,
2459
- height: H,
2460
- style: { display: "block", cursor: "grab", borderRadius: 6 },
2461
- onMouseDown,
2462
- onMouseMove,
2463
- onMouseUp,
2464
- onMouseLeave: onMouseUp,
2465
- children: [
2466
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { width: W, height: H, rx: 6, fill: isDark ? "#0f172a" : "#fafbfc" }),
2467
- boxes.map((b) => {
2468
- const p = project(b.x, b.y);
2469
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2470
- "rect",
2471
- {
2472
- x: p.x,
2473
- y: p.y,
2474
- width: Math.max(2, b.w * scale),
2475
- height: Math.max(2, b.h * scale),
2476
- rx: 2,
2477
- fill: nodeFill
2478
- },
2479
- b.id
2480
- );
2481
- }),
2482
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2483
- "rect",
2484
- {
2485
- x: vpRect.x,
2486
- y: vpRect.y,
2487
- width: vpRect.w,
2488
- height: vpRect.h,
2489
- rx: 3,
2490
- fill: viewFill,
2491
- stroke: viewStroke,
2492
- strokeWidth: 1.25
2493
- }
2494
- )
2495
- ]
2496
- }
2497
- )
2498
- }
2499
- );
2500
- }
2501
-
2502
- // src/ui/NodeNavigator.tsx
2503
- var import_react10 = require("react");
2504
- var import_jsx_runtime6 = require("react/jsx-runtime");
2505
- function NodeNavigator({
2506
- model,
2507
- selected,
2508
- variant,
2509
- isDark,
2510
- t,
2511
- acc,
2512
- open,
2513
- onToggle,
2514
- onSelect
2515
- }) {
2516
- const [search, setSearch] = (0, import_react10.useState)("");
2517
- const shapeIcon = (node) => {
2518
- if (variant === "question") return "?";
2519
- if (variant === "journey") return "\u2197";
2520
- switch (node.shape) {
2521
- case "diamond":
2522
- return "\u25C7";
2523
- case "circle":
2524
- return "\u25CB";
2525
- case "parallelogram":
2526
- return "\u25B1";
2527
- default:
2528
- return "\u25AD";
2529
- }
2458
+ const [search, setSearch] = (0, import_react11.useState)("");
2459
+ const shapeIcon = (node) => {
2460
+ if (variant === "question") return "?";
2461
+ if (variant === "journey") return "\u2197";
2462
+ switch (node.shape) {
2463
+ case "diamond":
2464
+ return "\u25C7";
2465
+ case "circle":
2466
+ return "\u25CB";
2467
+ case "parallelogram":
2468
+ return "\u25B1";
2469
+ default:
2470
+ return "\u25AD";
2471
+ }
2530
2472
  };
2531
2473
  const filtered = model.nodes.filter(
2532
2474
  (n) => n.label.toLowerCase().includes(search.toLowerCase())
@@ -2683,137 +2625,6 @@ function NodeNavigator({
2683
2625
  ] });
2684
2626
  }
2685
2627
 
2686
- // src/ui/ContextMenu.tsx
2687
- var import_react11 = require("react");
2688
- var import_jsx_runtime7 = require("react/jsx-runtime");
2689
- function ContextMenu({
2690
- x,
2691
- y,
2692
- nodeId,
2693
- edgeId,
2694
- isDark,
2695
- t,
2696
- acc,
2697
- canUndo,
2698
- canRedo,
2699
- onUndo,
2700
- onRedo,
2701
- onReCenter,
2702
- onAddNode,
2703
- onDuplicate,
2704
- onRename,
2705
- onDelete,
2706
- onDisconnect,
2707
- onEdgeRename,
2708
- onEdgeStyle,
2709
- onEdgeArrowhead,
2710
- onEdgeDelete,
2711
- onEdgeResetRouting,
2712
- currentEdgeStyle,
2713
- currentEdgeArrow,
2714
- edgeHasWaypoint,
2715
- containerRef
2716
- }) {
2717
- const menuRef = (0, import_react11.useRef)(null);
2718
- const [pos, setPos] = (0, import_react11.useState)({ x, y });
2719
- (0, import_react11.useEffect)(() => {
2720
- if (!menuRef.current || !containerRef.current) return;
2721
- const m = menuRef.current.getBoundingClientRect();
2722
- const c = containerRef.current.getBoundingClientRect();
2723
- let nx = x, ny = y;
2724
- if (nx + m.width > c.right - 8) nx = x - m.width;
2725
- if (ny + m.height > c.bottom - 8) ny = y - m.height;
2726
- setPos({ x: nx, y: ny });
2727
- }, [x, y, containerRef]);
2728
- const bg = isDark ? "#1e293b" : "#ffffff";
2729
- const border = isDark ? "#334155" : "#e2e8f0";
2730
- const hoverBg = isDark ? "#334155" : "#f1f5f9";
2731
- const dividerColor = isDark ? "#334155" : "#f1f5f9";
2732
- const text = t.textPrimary;
2733
- const muted = t.textMuted;
2734
- const item = (label, onClick, color, disabled) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2735
- "button",
2736
- {
2737
- onClick: disabled ? void 0 : onClick,
2738
- style: {
2739
- display: "flex",
2740
- alignItems: "center",
2741
- gap: 10,
2742
- width: "100%",
2743
- padding: "7px 14px",
2744
- background: "none",
2745
- border: "none",
2746
- textAlign: "left",
2747
- cursor: disabled ? "default" : "pointer",
2748
- fontSize: 12,
2749
- fontFamily: "ui-sans-serif,system-ui,sans-serif",
2750
- color: disabled ? muted : color ?? text,
2751
- opacity: disabled ? 0.4 : 1,
2752
- borderRadius: 6
2753
- },
2754
- onMouseEnter: (e) => {
2755
- if (!disabled) e.currentTarget.style.background = hoverBg;
2756
- },
2757
- onMouseLeave: (e) => {
2758
- e.currentTarget.style.background = "none";
2759
- },
2760
- children: label
2761
- },
2762
- label
2763
- );
2764
- const divider2 = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { height: 1, background: dividerColor, margin: "4px 0" } });
2765
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2766
- "div",
2767
- {
2768
- ref: menuRef,
2769
- onMouseDown: (e) => e.stopPropagation(),
2770
- style: {
2771
- position: "fixed",
2772
- left: pos.x,
2773
- top: pos.y,
2774
- zIndex: 9999,
2775
- background: bg,
2776
- border: `1px solid ${border}`,
2777
- borderRadius: 10,
2778
- padding: "5px 0",
2779
- minWidth: 180,
2780
- boxShadow: isDark ? "0 8px 32px rgba(0,0,0,0.5)" : "0 8px 32px rgba(0,0,0,0.12)",
2781
- fontFamily: "ui-sans-serif,system-ui,sans-serif"
2782
- },
2783
- children: edgeId ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2784
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Edge" }),
2785
- item("Rename label (dbl-click)", () => onEdgeRename?.()),
2786
- divider2,
2787
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Style" }),
2788
- item(`Solid${currentEdgeStyle === "solid" || !currentEdgeStyle ? " \u2713" : ""}`, () => onEdgeStyle?.("solid")),
2789
- item(`Dashed${currentEdgeStyle === "dashed" ? " \u2713" : ""}`, () => onEdgeStyle?.("dashed")),
2790
- item(`Dotted${currentEdgeStyle === "dotted" ? " \u2713" : ""}`, () => onEdgeStyle?.("dotted")),
2791
- divider2,
2792
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Arrowhead" }),
2793
- item(`Arrow${currentEdgeArrow !== "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("arrow")),
2794
- item(`None${currentEdgeArrow === "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("none")),
2795
- divider2,
2796
- item("Reset routing", () => onEdgeResetRouting?.(), void 0, !edgeHasWaypoint),
2797
- item("Delete edge", () => onEdgeDelete?.(), "#ef4444")
2798
- ] }) : nodeId ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2799
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Node" }),
2800
- item("Rename (dbl-click)", onRename),
2801
- item("Duplicate", onDuplicate),
2802
- item("Disconnect all edges", onDisconnect),
2803
- divider2,
2804
- item("Delete node", onDelete, "#ef4444")
2805
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2806
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Canvas" }),
2807
- item("Add node here", onAddNode, acc.color),
2808
- item("Re-center (Ctrl+0)", onReCenter),
2809
- divider2,
2810
- item("Undo (Ctrl+Z)", onUndo, void 0, !canUndo),
2811
- item("Redo (Ctrl+Y)", onRedo, void 0, !canRedo)
2812
- ] })
2813
- }
2814
- );
2815
- }
2816
-
2817
2628
  // src/ui/render.tsx
2818
2629
  var import_react12 = require("react");
2819
2630
 
@@ -2881,7 +2692,7 @@ function bezierPathVia(x1, y1, wx, wy, x2, y2) {
2881
2692
  }
2882
2693
 
2883
2694
  // src/ui/render.tsx
2884
- var import_jsx_runtime8 = require("react/jsx-runtime");
2695
+ var import_jsx_runtime7 = require("react/jsx-runtime");
2885
2696
  var STYLE_LABEL = { pointerEvents: "none", userSelect: "none" };
2886
2697
  var STYLE_BLUR = { filter: "blur(4px)" };
2887
2698
  var STYLE_EDGE_HIT = { cursor: "pointer" };
@@ -2895,11 +2706,11 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
2895
2706
  const stroke = selected ? acc.color : t.nodeStroke;
2896
2707
  const fill = selected ? t.nodeSelectedFill : t.nodeFill;
2897
2708
  const sw = selected ? 1.75 : 1.25;
2898
- const glow = selected && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: node.shape === "circle" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2899
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx, cy, r: NODE_H2 / 2 + 3, fill: "none", stroke: acc.color, strokeWidth: 6, opacity: 0.18, style: STYLE_BLUR }),
2900
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx, cy, r: NODE_H2 / 2 + 1.5, fill: "none", stroke: acc.color, strokeWidth: 1, opacity: 0.55 })
2901
- ] }) : node.shape === "diamond" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2902
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2709
+ const glow = selected && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: node.shape === "circle" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2710
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx, cy, r: NODE_H2 / 2 + 3, fill: "none", stroke: acc.color, strokeWidth: 6, opacity: 0.18, style: STYLE_BLUR }),
2711
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx, cy, r: NODE_H2 / 2 + 1.5, fill: "none", stroke: acc.color, strokeWidth: 1, opacity: 0.55 })
2712
+ ] }) : node.shape === "diamond" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2713
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2903
2714
  "polygon",
2904
2715
  {
2905
2716
  points: `${cx},${-5} ${w + 5},${cy} ${cx},${NODE_H2 + 5} ${-5},${cy}`,
@@ -2910,7 +2721,7 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
2910
2721
  style: STYLE_BLUR
2911
2722
  }
2912
2723
  ),
2913
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2724
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2914
2725
  "polygon",
2915
2726
  {
2916
2727
  points: `${cx},${-2} ${w + 2},${cy} ${cx},${NODE_H2 + 2} ${-2},${cy}`,
@@ -2920,8 +2731,8 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
2920
2731
  opacity: 0.55
2921
2732
  }
2922
2733
  )
2923
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2924
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2734
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2735
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2925
2736
  "rect",
2926
2737
  {
2927
2738
  x: -4,
@@ -2936,7 +2747,7 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
2936
2747
  style: STYLE_BLUR
2937
2748
  }
2938
2749
  ),
2939
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2750
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2940
2751
  "rect",
2941
2752
  {
2942
2753
  x: -1.5,
@@ -2952,35 +2763,35 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
2952
2763
  )
2953
2764
  ] }) });
2954
2765
  const badgeColor = isDark ? ACCENT.emeraldDark : ACCENT.emerald;
2955
- const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2956
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: 14, cy: 14, r: 10, fill: badgeColor }),
2957
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("text", { x: 14, y: 18, textAnchor: "middle", fontSize: 9, fill: "white", fontWeight: "700", style: STYLE_LABEL, children: stepNumber })
2766
+ const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2767
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: 14, cy: 14, r: 10, fill: badgeColor }),
2768
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("text", { x: 14, y: 18, textAnchor: "middle", fontSize: 9, fill: "white", fontWeight: "700", style: STYLE_LABEL, children: stepNumber })
2958
2769
  ] });
2959
2770
  switch (node.shape) {
2960
2771
  case "diamond": {
2961
2772
  const pts = `${cx},0 ${w},${cy} ${cx},${NODE_H2} 0,${cy}`;
2962
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2773
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2963
2774
  glow,
2964
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polygon", { points: pts, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
2775
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("polygon", { points: pts, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
2965
2776
  badge
2966
2777
  ] });
2967
2778
  }
2968
2779
  case "circle":
2969
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2780
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2970
2781
  glow,
2971
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx, cy, r: NODE_H2 / 2 - 1, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
2782
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx, cy, r: NODE_H2 / 2 - 1, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
2972
2783
  badge
2973
2784
  ] });
2974
2785
  case "parallelogram":
2975
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2786
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2976
2787
  glow,
2977
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polygon", { points: `14,0 ${w},0 ${w - 14},${NODE_H2} 0,${NODE_H2}`, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
2788
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("polygon", { points: `14,0 ${w},0 ${w - 14},${NODE_H2} 0,${NODE_H2}`, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
2978
2789
  badge
2979
2790
  ] });
2980
2791
  default:
2981
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2792
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2982
2793
  glow,
2983
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { width: w, height: NODE_H2, rx: 14, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
2794
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { width: w, height: NODE_H2, rx: 14, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
2984
2795
  badge
2985
2796
  ] });
2986
2797
  }
@@ -3001,8 +2812,8 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
3001
2812
  const textSub = isDark ? "#64748b" : "#94a3b8";
3002
2813
  const textAns = isDark ? "#cbd5e1" : "#374151";
3003
2814
  const portRowY = Q_BASE_H2 + Q_ANS_ROW_H2 - 8;
3004
- const glow = selected && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3005
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2815
+ const glow = selected && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2816
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3006
2817
  "rect",
3007
2818
  {
3008
2819
  x: -4,
@@ -3017,7 +2828,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
3017
2828
  style: STYLE_BLUR
3018
2829
  }
3019
2830
  ),
3020
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2831
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3021
2832
  "rect",
3022
2833
  {
3023
2834
  x: -1.5,
@@ -3032,29 +2843,29 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
3032
2843
  }
3033
2844
  )
3034
2845
  ] });
3035
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2846
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3036
2847
  glow,
3037
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { width: qW, height: totalH, rx: 14, fill: nodeBg, stroke: nodeBorder, strokeWidth: selected ? 2 : 1.5, filter: "url(#nodeShadow)" }),
3038
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("clipPath", { id: `qhdr-${node.id}`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { width: qW, height: Q_BASE_H2, rx: 14 }) }),
3039
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { width: qW, height: Q_BASE_H2, fill: amberSoft, clipPath: `url(#qhdr-${node.id})` }),
3040
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: 0, y: 0, width: 4, height: Q_BASE_H2, rx: 2, fill: amber }),
3041
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: 12, y: 14, width: 28, height: 28, rx: 8, fill: amber }),
3042
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("text", { x: 26, y: 33, textAnchor: "middle", fontSize: 15, fontWeight: "900", fill: "white", style: STYLE_LABEL, children: "?" }),
3043
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2848
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { width: qW, height: totalH, rx: 14, fill: nodeBg, stroke: nodeBorder, strokeWidth: selected ? 2 : 1.5, filter: "url(#nodeShadow)" }),
2849
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("clipPath", { id: `qhdr-${node.id}`, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { width: qW, height: Q_BASE_H2, rx: 14 }) }),
2850
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { width: qW, height: Q_BASE_H2, fill: amberSoft, clipPath: `url(#qhdr-${node.id})` }),
2851
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: 0, y: 0, width: 4, height: Q_BASE_H2, rx: 2, fill: amber }),
2852
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: 12, y: 14, width: 28, height: 28, rx: 8, fill: amber }),
2853
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("text", { x: 26, y: 33, textAnchor: "middle", fontSize: 15, fontWeight: "900", fill: "white", style: STYLE_LABEL, children: "?" }),
2854
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3044
2855
  "text",
3045
2856
  {
3046
2857
  style: STYLE_LABEL,
3047
2858
  fontFamily: "ui-sans-serif,system-ui,sans-serif",
3048
2859
  children: [
3049
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tspan", { x: 50, y: 27, fontSize: 9, fontWeight: 700, fill: textSub, letterSpacing: 0.6, textAnchor: "start", children: "QUESTION" }),
3050
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tspan", { x: 50, dy: 15, fontSize: 13, fontWeight: 700, fill: selected ? amber : textMain, textAnchor: "start", children: node.label })
2860
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tspan", { x: 50, y: 27, fontSize: 9, fontWeight: 700, fill: textSub, letterSpacing: 0.6, textAnchor: "start", children: "QUESTION" }),
2861
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tspan", { x: 50, dy: 15, fontSize: 13, fontWeight: 700, fill: selected ? amber : textMain, textAnchor: "start", children: node.label })
3051
2862
  ]
3052
2863
  }
3053
2864
  ),
3054
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: 0, y1: Q_BASE_H2, x2: qW, y2: Q_BASE_H2, stroke: amberLine, strokeWidth: 1 }),
3055
- answers.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3056
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("text", { x: qW / 2, y: Q_BASE_H2 + 22, textAnchor: "middle", fontSize: 10, fill: amber, opacity: 0.4, fontWeight: 600, style: STYLE_LABEL, children: "No answers yet" }),
3057
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("text", { x: qW / 2, y: Q_BASE_H2 + 36, textAnchor: "middle", fontSize: 9, fill: textSub, opacity: 0.7, style: STYLE_LABEL, children: "Open panel \u2192 Add Answer" })
2865
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: 0, y1: Q_BASE_H2, x2: qW, y2: Q_BASE_H2, stroke: amberLine, strokeWidth: 1 }),
2866
+ answers.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2867
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("text", { x: qW / 2, y: Q_BASE_H2 + 22, textAnchor: "middle", fontSize: 10, fill: amber, opacity: 0.4, fontWeight: 600, style: STYLE_LABEL, children: "No answers yet" }),
2868
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("text", { x: qW / 2, y: Q_BASE_H2 + 36, textAnchor: "middle", fontSize: 9, fill: textSub, opacity: 0.7, style: STYLE_LABEL, children: "Open panel \u2192 Add Answer" })
3058
2869
  ] }),
3059
2870
  answers.map((ans, i) => {
3060
2871
  const prevW = answers.slice(0, i).reduce((s2, a) => s2 + answerCardW2(a) + Q_CARD_PAD2, 0);
@@ -3067,8 +2878,8 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
3067
2878
  const letter = i < 26 ? ANSWER_LETTERS[i] : `${i + 1}`;
3068
2879
  const maxChars = Math.max(2, Math.floor((cW - 20) / 7.5));
3069
2880
  const displayAns = ans.length > maxChars ? ans.slice(0, maxChars - 1) + "\u2026" : ans;
3070
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("g", { children: [
3071
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2881
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("g", { children: [
2882
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3072
2883
  "rect",
3073
2884
  {
3074
2885
  x: cardX,
@@ -3081,7 +2892,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
3081
2892
  strokeWidth: connected ? 1.5 : 1
3082
2893
  }
3083
2894
  ),
3084
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2895
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3085
2896
  "rect",
3086
2897
  {
3087
2898
  x: cx - 11,
@@ -3092,7 +2903,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
3092
2903
  fill: connected ? amber : isDark ? "#1e293b" : "#fef3c7"
3093
2904
  }
3094
2905
  ),
3095
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2906
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3096
2907
  "text",
3097
2908
  {
3098
2909
  x: cx,
@@ -3105,7 +2916,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
3105
2916
  children: letter
3106
2917
  }
3107
2918
  ),
3108
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2919
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3109
2920
  "text",
3110
2921
  {
3111
2922
  x: cx,
@@ -3119,7 +2930,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
3119
2930
  children: displayAns
3120
2931
  }
3121
2932
  ),
3122
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2933
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3123
2934
  "circle",
3124
2935
  {
3125
2936
  cx,
@@ -3132,7 +2943,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
3132
2943
  onMouseDown: (e) => onAnswerPortDown(e, node.id, ans, cx, portRowY)
3133
2944
  }
3134
2945
  ),
3135
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2946
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3136
2947
  "path",
3137
2948
  {
3138
2949
  d: `M ${cx - 3} ${portRowY - 2} L ${cx} ${portRowY + 2} L ${cx + 3} ${portRowY - 2}`,
@@ -3188,7 +2999,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
3188
2999
  const labelW = edge.label ? Math.max(60, Math.ceil(estimateTextW2(edge.label, 7) + 18)) : 60;
3189
3000
  const showHandle = !!onWaypointDown && (hovered || !!wp);
3190
3001
  const flowClass = dash ? void 0 : isAmber ? "edge-flow-amber" : "edge-flow";
3191
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3002
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3192
3003
  "g",
3193
3004
  {
3194
3005
  onDoubleClick: (e) => {
@@ -3201,8 +3012,8 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
3201
3012
  onMouseEnter: () => setHovered(true),
3202
3013
  onMouseLeave: () => setHovered(false),
3203
3014
  children: [
3204
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d, fill: "none", stroke: "transparent", strokeWidth: 14, style: STYLE_EDGE_HIT }),
3205
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3015
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d, fill: "none", stroke: "transparent", strokeWidth: 14, style: STYLE_EDGE_HIT }),
3016
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3206
3017
  "path",
3207
3018
  {
3208
3019
  d,
@@ -3217,7 +3028,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
3217
3028
  style: STYLE_NO_EVENTS
3218
3029
  }
3219
3030
  ),
3220
- showHandle && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3031
+ showHandle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3221
3032
  "circle",
3222
3033
  {
3223
3034
  cx: hx,
@@ -3233,7 +3044,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
3233
3044
  }
3234
3045
  }
3235
3046
  ),
3236
- editing && !isAmber ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("foreignObject", { x: mx - labelW / 2, y: my - 12, width: labelW, height: 22, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3047
+ editing && !isAmber ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("foreignObject", { x: mx - labelW / 2, y: my - 12, width: labelW, height: 22, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3237
3048
  "input",
3238
3049
  {
3239
3050
  autoFocus: true,
@@ -3245,80 +3056,664 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
3245
3056
  e.preventDefault();
3246
3057
  onEditCommit?.();
3247
3058
  }
3248
- if (e.key === "Escape") {
3249
- e.preventDefault();
3250
- onEditCancel?.();
3059
+ if (e.key === "Escape") {
3060
+ e.preventDefault();
3061
+ onEditCancel?.();
3062
+ }
3063
+ },
3064
+ onMouseDown: (e) => e.stopPropagation(),
3065
+ style: {
3066
+ width: "100%",
3067
+ height: "100%",
3068
+ border: "none",
3069
+ borderRadius: 6,
3070
+ outline: `2px solid ${acc.color}`,
3071
+ textAlign: "center",
3072
+ fontSize: 10,
3073
+ fontWeight: 500,
3074
+ background: t.inputBg,
3075
+ color: t.inputText,
3076
+ boxSizing: "border-box",
3077
+ padding: "0 6px",
3078
+ fontFamily: "inherit"
3079
+ }
3080
+ }
3081
+ ) }) : edge.label && !isAmber ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3082
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3083
+ "rect",
3084
+ {
3085
+ x: mx - labelW / 2,
3086
+ y: my - 11,
3087
+ width: labelW,
3088
+ height: 19,
3089
+ rx: 5,
3090
+ fill: t.panelBg,
3091
+ stroke: t.cardBorder,
3092
+ strokeWidth: 1,
3093
+ style: STYLE_EDGE_LABEL_HIT
3094
+ }
3095
+ ),
3096
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3097
+ "text",
3098
+ {
3099
+ x: mx,
3100
+ y: my + 4,
3101
+ textAnchor: "middle",
3102
+ fontSize: 10,
3103
+ fill: t.textSecondary,
3104
+ fontFamily: "ui-sans-serif,system-ui,sans-serif",
3105
+ fontWeight: "500",
3106
+ style: STYLE_LABEL,
3107
+ children: edge.label
3108
+ }
3109
+ )
3110
+ ] }) : null
3111
+ ]
3112
+ }
3113
+ );
3114
+ }
3115
+
3116
+ // src/ui/Minimap.tsx
3117
+ var import_react13 = require("react");
3118
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3119
+ var W = 168;
3120
+ var H = 112;
3121
+ var PAD = 18;
3122
+ function Minimap({
3123
+ model,
3124
+ viewportW,
3125
+ viewportH,
3126
+ transform,
3127
+ measureNode,
3128
+ onCenterOn,
3129
+ isDark,
3130
+ accentColor
3131
+ }) {
3132
+ const dragRef = (0, import_react13.useRef)(null);
3133
+ const boxes = model.nodes.map((n) => {
3134
+ const { w, h } = measureNode(n);
3135
+ return { id: n.id, x: n.x ?? 0, y: n.y ?? 0, w, h };
3136
+ });
3137
+ if (boxes.length === 0) return null;
3138
+ const vx = -transform.x / transform.scale;
3139
+ const vy = -transform.y / transform.scale;
3140
+ const vw = viewportW / transform.scale;
3141
+ const vh = viewportH / transform.scale;
3142
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
3143
+ for (const b of boxes) {
3144
+ minX = Math.min(minX, b.x);
3145
+ minY = Math.min(minY, b.y);
3146
+ maxX = Math.max(maxX, b.x + b.w);
3147
+ maxY = Math.max(maxY, b.y + b.h);
3148
+ }
3149
+ minX = Math.min(minX, vx);
3150
+ minY = Math.min(minY, vy);
3151
+ maxX = Math.max(maxX, vx + vw);
3152
+ maxY = Math.max(maxY, vy + vh);
3153
+ const contentW = Math.max(1, maxX - minX);
3154
+ const contentH = Math.max(1, maxY - minY);
3155
+ const scale = Math.min((W - PAD * 2) / contentW, (H - PAD * 2) / contentH);
3156
+ const offsetX = (W - contentW * scale) / 2 - minX * scale;
3157
+ const offsetY = (H - contentH * scale) / 2 - minY * scale;
3158
+ const project = (x, y) => ({
3159
+ x: offsetX + x * scale,
3160
+ y: offsetY + y * scale
3161
+ });
3162
+ const unproject = (mx, my) => ({
3163
+ x: (mx - offsetX) / scale,
3164
+ y: (my - offsetY) / scale
3165
+ });
3166
+ const panTo = (0, import_react13.useCallback)((e) => {
3167
+ const rect = e.currentTarget.getBoundingClientRect();
3168
+ const mx = e.clientX - rect.left;
3169
+ const my = e.clientY - rect.top;
3170
+ const { x, y } = unproject(mx, my);
3171
+ onCenterOn(x, y);
3172
+ }, [onCenterOn, scale, offsetX, offsetY]);
3173
+ const onMouseDown = (e) => {
3174
+ e.stopPropagation();
3175
+ dragRef.current = { active: true };
3176
+ panTo(e);
3177
+ };
3178
+ const onMouseMove = (e) => {
3179
+ if (!dragRef.current?.active) return;
3180
+ panTo(e);
3181
+ };
3182
+ const onMouseUp = () => {
3183
+ dragRef.current = null;
3184
+ };
3185
+ const bg = isDark ? "rgba(15,23,42,0.92)" : "rgba(255,255,255,0.94)";
3186
+ const border = isDark ? "#334155" : "#e2e8f0";
3187
+ const nodeFill = isDark ? "#475569" : "#cbd5e1";
3188
+ const viewStroke = accentColor;
3189
+ const viewFill = `${accentColor}22`;
3190
+ const vp1 = project(vx, vy);
3191
+ const vp2 = project(vx + vw, vy + vh);
3192
+ const vpRect = {
3193
+ x: Math.max(0, Math.min(W, vp1.x)),
3194
+ y: Math.max(0, Math.min(H, vp1.y)),
3195
+ w: Math.max(2, Math.min(W, vp2.x) - Math.max(0, vp1.x)),
3196
+ h: Math.max(2, Math.min(H, vp2.y) - Math.max(0, vp1.y))
3197
+ };
3198
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3199
+ "div",
3200
+ {
3201
+ style: {
3202
+ position: "absolute",
3203
+ bottom: 14,
3204
+ right: 14,
3205
+ background: bg,
3206
+ border: `1px solid ${border}`,
3207
+ borderRadius: 10,
3208
+ padding: 6,
3209
+ boxShadow: isDark ? "0 8px 20px rgba(0,0,0,0.45)" : "0 6px 18px rgba(15,23,42,0.08)",
3210
+ backdropFilter: "blur(6px)"
3211
+ },
3212
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3213
+ "svg",
3214
+ {
3215
+ width: W,
3216
+ height: H,
3217
+ style: { display: "block", cursor: "grab", borderRadius: 6 },
3218
+ onMouseDown,
3219
+ onMouseMove,
3220
+ onMouseUp,
3221
+ onMouseLeave: onMouseUp,
3222
+ children: [
3223
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { width: W, height: H, rx: 6, fill: isDark ? "#0f172a" : "#fafbfc" }),
3224
+ boxes.map((b) => {
3225
+ const p = project(b.x, b.y);
3226
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3227
+ "rect",
3228
+ {
3229
+ x: p.x,
3230
+ y: p.y,
3231
+ width: Math.max(2, b.w * scale),
3232
+ height: Math.max(2, b.h * scale),
3233
+ rx: 2,
3234
+ fill: nodeFill
3235
+ },
3236
+ b.id
3237
+ );
3238
+ }),
3239
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3240
+ "rect",
3241
+ {
3242
+ x: vpRect.x,
3243
+ y: vpRect.y,
3244
+ width: vpRect.w,
3245
+ height: vpRect.h,
3246
+ rx: 3,
3247
+ fill: viewFill,
3248
+ stroke: viewStroke,
3249
+ strokeWidth: 1.25
3250
+ }
3251
+ )
3252
+ ]
3253
+ }
3254
+ )
3255
+ }
3256
+ );
3257
+ }
3258
+
3259
+ // src/ui/ContextMenu.tsx
3260
+ var import_react14 = require("react");
3261
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3262
+ function ContextMenu({
3263
+ x,
3264
+ y,
3265
+ nodeId,
3266
+ edgeId,
3267
+ isDark,
3268
+ t,
3269
+ acc,
3270
+ canUndo,
3271
+ canRedo,
3272
+ onUndo,
3273
+ onRedo,
3274
+ onReCenter,
3275
+ onAddNode,
3276
+ onDuplicate,
3277
+ onRename,
3278
+ onDelete,
3279
+ onDisconnect,
3280
+ onEdgeRename,
3281
+ onEdgeStyle,
3282
+ onEdgeArrowhead,
3283
+ onEdgeDelete,
3284
+ onEdgeResetRouting,
3285
+ currentEdgeStyle,
3286
+ currentEdgeArrow,
3287
+ edgeHasWaypoint,
3288
+ containerRef
3289
+ }) {
3290
+ const menuRef = (0, import_react14.useRef)(null);
3291
+ const [pos, setPos] = (0, import_react14.useState)({ x, y });
3292
+ (0, import_react14.useEffect)(() => {
3293
+ if (!menuRef.current || !containerRef.current) return;
3294
+ const m = menuRef.current.getBoundingClientRect();
3295
+ const c = containerRef.current.getBoundingClientRect();
3296
+ let nx = x, ny = y;
3297
+ if (nx + m.width > c.right - 8) nx = x - m.width;
3298
+ if (ny + m.height > c.bottom - 8) ny = y - m.height;
3299
+ setPos({ x: nx, y: ny });
3300
+ }, [x, y, containerRef]);
3301
+ const bg = isDark ? "#1e293b" : "#ffffff";
3302
+ const border = isDark ? "#334155" : "#e2e8f0";
3303
+ const hoverBg = isDark ? "#334155" : "#f1f5f9";
3304
+ const dividerColor = isDark ? "#334155" : "#f1f5f9";
3305
+ const text = t.textPrimary;
3306
+ const muted = t.textMuted;
3307
+ const item = (label, onClick, color, disabled) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3308
+ "button",
3309
+ {
3310
+ onClick: disabled ? void 0 : onClick,
3311
+ style: {
3312
+ display: "flex",
3313
+ alignItems: "center",
3314
+ gap: 10,
3315
+ width: "100%",
3316
+ padding: "7px 14px",
3317
+ background: "none",
3318
+ border: "none",
3319
+ textAlign: "left",
3320
+ cursor: disabled ? "default" : "pointer",
3321
+ fontSize: 12,
3322
+ fontFamily: "ui-sans-serif,system-ui,sans-serif",
3323
+ color: disabled ? muted : color ?? text,
3324
+ opacity: disabled ? 0.4 : 1,
3325
+ borderRadius: 6
3326
+ },
3327
+ onMouseEnter: (e) => {
3328
+ if (!disabled) e.currentTarget.style.background = hoverBg;
3329
+ },
3330
+ onMouseLeave: (e) => {
3331
+ e.currentTarget.style.background = "none";
3332
+ },
3333
+ children: label
3334
+ },
3335
+ label
3336
+ );
3337
+ const divider2 = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { height: 1, background: dividerColor, margin: "4px 0" } });
3338
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3339
+ "div",
3340
+ {
3341
+ ref: menuRef,
3342
+ onMouseDown: (e) => e.stopPropagation(),
3343
+ style: {
3344
+ position: "fixed",
3345
+ left: pos.x,
3346
+ top: pos.y,
3347
+ zIndex: 9999,
3348
+ background: bg,
3349
+ border: `1px solid ${border}`,
3350
+ borderRadius: 10,
3351
+ padding: "5px 0",
3352
+ minWidth: 180,
3353
+ boxShadow: isDark ? "0 8px 32px rgba(0,0,0,0.5)" : "0 8px 32px rgba(0,0,0,0.12)",
3354
+ fontFamily: "ui-sans-serif,system-ui,sans-serif"
3355
+ },
3356
+ children: edgeId ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
3357
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Edge" }),
3358
+ item("Rename label (dbl-click)", () => onEdgeRename?.()),
3359
+ divider2,
3360
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Style" }),
3361
+ item(`Solid${currentEdgeStyle === "solid" || !currentEdgeStyle ? " \u2713" : ""}`, () => onEdgeStyle?.("solid")),
3362
+ item(`Dashed${currentEdgeStyle === "dashed" ? " \u2713" : ""}`, () => onEdgeStyle?.("dashed")),
3363
+ item(`Dotted${currentEdgeStyle === "dotted" ? " \u2713" : ""}`, () => onEdgeStyle?.("dotted")),
3364
+ divider2,
3365
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Arrowhead" }),
3366
+ item(`Arrow${currentEdgeArrow !== "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("arrow")),
3367
+ item(`None${currentEdgeArrow === "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("none")),
3368
+ divider2,
3369
+ item("Reset routing", () => onEdgeResetRouting?.(), void 0, !edgeHasWaypoint),
3370
+ item("Delete edge", () => onEdgeDelete?.(), "#ef4444")
3371
+ ] }) : nodeId ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
3372
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Node" }),
3373
+ item("Rename (dbl-click)", onRename),
3374
+ item("Duplicate", onDuplicate),
3375
+ item("Disconnect all edges", onDisconnect),
3376
+ divider2,
3377
+ item("Delete node", onDelete, "#ef4444")
3378
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
3379
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Canvas" }),
3380
+ item("Add node here", onAddNode, acc.color),
3381
+ item("Re-center (Ctrl+0)", onReCenter),
3382
+ divider2,
3383
+ item("Undo (Ctrl+Z)", onUndo, void 0, !canUndo),
3384
+ item("Redo (Ctrl+Y)", onRedo, void 0, !canRedo)
3385
+ ] })
3386
+ }
3387
+ );
3388
+ }
3389
+
3390
+ // src/ui/DiagramCanvas.tsx
3391
+ var import_jsx_runtime10 = require("react/jsx-runtime");
3392
+ var STYLE_LABEL2 = { pointerEvents: "none", userSelect: "none" };
3393
+ var STYLE_LIVE_PORT = { opacity: 0.85, pointerEvents: "none" };
3394
+ var STYLE_NODE_GRAB = { cursor: "grab" };
3395
+ var STYLE_NODE_GRABBING = { cursor: "grabbing" };
3396
+ var STYLE_PORT_VISIBLE = { cursor: "crosshair", opacity: 1, transition: "opacity 0.15s", pointerEvents: "all", filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))" };
3397
+ var STYLE_PORT_HIDDEN = { cursor: "crosshair", opacity: 0, transition: "opacity 0.15s", pointerEvents: "none", filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))" };
3398
+ function DiagramCanvas(props) {
3399
+ const {
3400
+ model,
3401
+ variant,
3402
+ variantLabel,
3403
+ t,
3404
+ isDark,
3405
+ acc,
3406
+ transform,
3407
+ setTransform,
3408
+ selected,
3409
+ selectedSet,
3410
+ hoveredId,
3411
+ setHoveredId,
3412
+ drag,
3413
+ pan,
3414
+ liveEdge,
3415
+ boxSel,
3416
+ alignGuides,
3417
+ editingEdgeId,
3418
+ editEdgeLabel,
3419
+ setEditEdgeLabel,
3420
+ commitEdgeEdit,
3421
+ setEditingEdgeId,
3422
+ beginEditEdge,
3423
+ onEdgeContextMenu,
3424
+ setWaypointDrag,
3425
+ editingId,
3426
+ editLabel,
3427
+ setEditLabel,
3428
+ commitEdit,
3429
+ setEditingId,
3430
+ onNodeMouseDown,
3431
+ onNodeMouseUp,
3432
+ onNodeDblClick,
3433
+ onNodeContextMenu,
3434
+ onPortMouseDown,
3435
+ onAnswerPortDown,
3436
+ onSvgMouseDown,
3437
+ onMouseMove,
3438
+ onMouseUp,
3439
+ onSvgContextMenu,
3440
+ reducedMotion,
3441
+ isCoarse,
3442
+ portR,
3443
+ shadowClr,
3444
+ arrowClr,
3445
+ amberArrow,
3446
+ viewport,
3447
+ svgRef,
3448
+ containerRef,
3449
+ ctxMenu,
3450
+ history,
3451
+ onCtxUndo,
3452
+ onCtxRedo,
3453
+ onCtxReCenter,
3454
+ onCtxAddNode,
3455
+ onCtxDuplicate,
3456
+ onCtxRename,
3457
+ onCtxDelete,
3458
+ onCtxDisconnect,
3459
+ ctxEdgeStyle,
3460
+ ctxEdgeArrow,
3461
+ ctxEdgeHasWaypoint,
3462
+ onCtxEdgeRename,
3463
+ onCtxEdgeStyle,
3464
+ onCtxEdgeArrowhead,
3465
+ onCtxEdgeDelete,
3466
+ onCtxEdgeResetRouting
3467
+ } = props;
3468
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref: containerRef, style: { flex: 1, overflow: "hidden", position: "relative", background: t.canvas }, children: [
3469
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3470
+ "svg",
3471
+ {
3472
+ ref: svgRef,
3473
+ width: "100%",
3474
+ height: "100%",
3475
+ role: "application",
3476
+ "aria-label": `${variantLabel} diagram editor. ${model.nodes.length} ${variantLabel.toLowerCase()}s, ${model.edges.length} connections. Scroll to zoom, drag to pan, click a ${variantLabel.toLowerCase()} to select.`,
3477
+ tabIndex: 0,
3478
+ style: { display: "block", cursor: pan ? "grabbing" : drag ? "grabbing" : liveEdge ? "crosshair" : "default", userSelect: "none", outline: "none" },
3479
+ onMouseDown: onSvgMouseDown,
3480
+ onMouseMove,
3481
+ onMouseUp,
3482
+ onMouseLeave: onMouseUp,
3483
+ onContextMenu: onSvgContextMenu,
3484
+ children: [
3485
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("defs", { children: [
3486
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: reducedMotion ? `
3487
+ .edge-flow { stroke-dasharray: 0; }
3488
+ .edge-flow-amber { stroke-dasharray: 0; }
3489
+ .edge-live { stroke-dasharray: 4 4; }
3490
+ ` : `
3491
+ @keyframes edgeFlow { to { stroke-dashoffset: -13; } }
3492
+ @keyframes edgeFlowFast { to { stroke-dashoffset: -13; } }
3493
+ .edge-flow { stroke-dasharray: 8 5; animation: edgeFlow 0.9s linear infinite; }
3494
+ .edge-flow-amber { stroke-dasharray: 6 4; animation: edgeFlowFast 0.65s linear infinite; }
3495
+ .edge-live { stroke-dasharray: 7 5; animation: edgeFlow 0.55s linear infinite; }
3496
+ ` }),
3497
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pattern", { id: "dots", width: GRID, height: GRID, patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { cx: GRID / 2, cy: GRID / 2, r: 1.1, fill: t.dot }) }),
3498
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("filter", { id: "nodeShadow", x: "-25%", y: "-25%", width: "150%", height: "160%", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("feDropShadow", { dx: "0", dy: "3", stdDeviation: "5", floodColor: shadowClr, floodOpacity: "1" }) }),
3499
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("marker", { id: "arrowhead", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: arrowClr }) }),
3500
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("marker", { id: "arrowAmber", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: amberArrow }) }),
3501
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("marker", { id: "arrowLive", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: acc.color }) })
3502
+ ] }),
3503
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { width: "100%", height: "100%", fill: "url(#dots)", "data-bg": "1" }),
3504
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("g", { transform: `translate(${transform.x},${transform.y}) scale(${transform.scale})`, children: [
3505
+ model.edges.map((e) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3506
+ EdgeLine,
3507
+ {
3508
+ edge: e,
3509
+ nodes: model.nodes,
3510
+ variant,
3511
+ t,
3512
+ isDark,
3513
+ acc,
3514
+ editing: editingEdgeId === e.id,
3515
+ editValue: editEdgeLabel,
3516
+ onEditChange: setEditEdgeLabel,
3517
+ onEditCommit: commitEdgeEdit,
3518
+ onEditCancel: () => setEditingEdgeId(null),
3519
+ onDoubleClick: beginEditEdge,
3520
+ onContextMenu: onEdgeContextMenu,
3521
+ onWaypointDown: (ev, edgeId) => setWaypointDrag(edgeId)
3522
+ },
3523
+ e.id
3524
+ )),
3525
+ liveEdge && (() => {
3526
+ const d = bezierPath2(liveEdge.fromX, liveEdge.fromY, liveEdge.toX, liveEdge.toY, liveEdge.exitDir);
3527
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d, fill: "none", stroke: acc.color, strokeWidth: 2, strokeLinecap: "round", className: "edge-live", opacity: 0.8, markerEnd: "url(#arrowLive)" });
3528
+ })(),
3529
+ alignGuides?.x && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3530
+ "line",
3531
+ {
3532
+ x1: alignGuides.x.pos,
3533
+ x2: alignGuides.x.pos,
3534
+ y1: alignGuides.x.minY,
3535
+ y2: alignGuides.x.maxY,
3536
+ stroke: acc.color,
3537
+ strokeWidth: 1 / transform.scale,
3538
+ strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
3539
+ opacity: 0.85,
3540
+ pointerEvents: "none"
3541
+ }
3542
+ ),
3543
+ alignGuides?.y && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3544
+ "line",
3545
+ {
3546
+ y1: alignGuides.y.pos,
3547
+ y2: alignGuides.y.pos,
3548
+ x1: alignGuides.y.minX,
3549
+ x2: alignGuides.y.maxX,
3550
+ stroke: acc.color,
3551
+ strokeWidth: 1 / transform.scale,
3552
+ strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
3553
+ opacity: 0.85,
3554
+ pointerEvents: "none"
3251
3555
  }
3252
- },
3253
- onMouseDown: (e) => e.stopPropagation(),
3254
- style: {
3255
- width: "100%",
3256
- height: "100%",
3257
- border: "none",
3258
- borderRadius: 6,
3259
- outline: `2px solid ${acc.color}`,
3260
- textAlign: "center",
3261
- fontSize: 10,
3262
- fontWeight: 500,
3263
- background: t.inputBg,
3264
- color: t.inputText,
3265
- boxSizing: "border-box",
3266
- padding: "0 6px",
3267
- fontFamily: "inherit"
3268
- }
3556
+ ),
3557
+ model.nodes.map((node, idx) => {
3558
+ const isHovered = hoveredId === node.id;
3559
+ const isQuestion2 = variant === "question";
3560
+ const { w: nW } = nodeDims(node, variant);
3561
+ const isSelected = selectedSet.has(node.id);
3562
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3563
+ "g",
3564
+ {
3565
+ transform: `translate(${node.x ?? 0},${node.y ?? 0})`,
3566
+ role: "button",
3567
+ "aria-label": `${variantLabel} ${variant === "journey" ? idx + 1 + ": " : ""}${node.label}${isSelected ? ", selected" : ""}`,
3568
+ style: drag?.nodeId === node.id ? STYLE_NODE_GRABBING : STYLE_NODE_GRAB,
3569
+ onMouseDown: (e) => onNodeMouseDown(e, node.id),
3570
+ onMouseUp: (e) => onNodeMouseUp(e, node.id),
3571
+ onDoubleClick: (e) => onNodeDblClick(e, node.id),
3572
+ onContextMenu: (e) => onNodeContextMenu(e, node.id),
3573
+ onMouseEnter: () => setHoveredId(node.id),
3574
+ onMouseLeave: () => setHoveredId(null),
3575
+ children: [
3576
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: `${variantLabel}: ${node.label}` }),
3577
+ isQuestion2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(QuestionNode, { node, selected: isSelected, edges: model.edges, isDark, onAnswerPortDown, qW: nW }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3578
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(NodeShape, { node, selected: isSelected, variant, stepNumber: variant === "journey" ? idx + 1 : void 0, t, isDark, w: nW }),
3579
+ editingId === node.id ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("foreignObject", { x: 6, y: 6, width: nW - 12, height: NODE_H2 - 12, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3580
+ "input",
3581
+ {
3582
+ autoFocus: true,
3583
+ value: editLabel,
3584
+ onChange: (e) => setEditLabel(e.target.value),
3585
+ onBlur: commitEdit,
3586
+ onKeyDown: (e) => {
3587
+ if (e.key === "Enter") commitEdit();
3588
+ if (e.key === "Escape") setEditingId(null);
3589
+ },
3590
+ style: { width: "100%", height: "100%", border: "none", borderRadius: 6, outline: `2px solid ${acc.color}`, textAlign: "center", fontSize: 13, fontWeight: 500, background: t.inputBg, boxSizing: "border-box", padding: "0 6px", fontFamily: "inherit", color: t.inputText }
3591
+ }
3592
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("text", { x: nW / 2, y: NODE_H2 / 2 + 5, textAnchor: "middle", fontSize: 13, fontWeight: "500", fontFamily: "ui-sans-serif,system-ui,sans-serif", fill: isSelected ? acc.color : t.textPrimary, style: STYLE_LABEL2, children: node.label }),
3593
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3594
+ "circle",
3595
+ {
3596
+ cx: nW / 2,
3597
+ cy: NODE_H2 + 1,
3598
+ r: portR,
3599
+ fill: acc.color,
3600
+ stroke: isDark ? "#0f172a" : "white",
3601
+ strokeWidth: 2,
3602
+ style: isHovered || isCoarse ? STYLE_PORT_VISIBLE : STYLE_PORT_HIDDEN,
3603
+ onMouseDown: (e) => onPortMouseDown(e, node.id)
3604
+ }
3605
+ )
3606
+ ] }),
3607
+ liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { cx: nW / 2, cy: -1, r: portR, fill: acc.color, stroke: isDark ? "#0f172a" : "white", strokeWidth: 2, style: STYLE_LIVE_PORT })
3608
+ ]
3609
+ },
3610
+ node.id
3611
+ );
3612
+ })
3613
+ ] })
3614
+ ]
3615
+ }
3616
+ ),
3617
+ boxSel && Math.abs(boxSel.cx - boxSel.sx) + Math.abs(boxSel.cy - boxSel.sy) > 4 && containerRef.current && (() => {
3618
+ const rect = containerRef.current.getBoundingClientRect();
3619
+ const left = Math.min(boxSel.sx, boxSel.cx) - rect.left;
3620
+ const top = Math.min(boxSel.sy, boxSel.cy) - rect.top;
3621
+ const w = Math.abs(boxSel.cx - boxSel.sx);
3622
+ const h = Math.abs(boxSel.cy - boxSel.sy);
3623
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3624
+ "div",
3625
+ {
3626
+ style: {
3627
+ position: "absolute",
3628
+ left,
3629
+ top,
3630
+ width: w,
3631
+ height: h,
3632
+ border: `1px dashed ${acc.color}`,
3633
+ background: isDark ? "rgba(99,102,241,0.10)" : "rgba(99,102,241,0.08)",
3634
+ pointerEvents: "none",
3635
+ borderRadius: 4
3269
3636
  }
3270
- ) }) : edge.label && !isAmber ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3271
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3272
- "rect",
3273
- {
3274
- x: mx - labelW / 2,
3275
- y: my - 11,
3276
- width: labelW,
3277
- height: 19,
3278
- rx: 5,
3279
- fill: t.panelBg,
3280
- stroke: t.cardBorder,
3281
- strokeWidth: 1,
3282
- style: STYLE_EDGE_LABEL_HIT
3283
- }
3284
- ),
3285
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3286
- "text",
3287
- {
3288
- x: mx,
3289
- y: my + 4,
3290
- textAnchor: "middle",
3291
- fontSize: 10,
3292
- fill: t.textSecondary,
3293
- fontFamily: "ui-sans-serif,system-ui,sans-serif",
3294
- fontWeight: "500",
3295
- style: STYLE_LABEL,
3296
- children: edge.label
3297
- }
3298
- )
3299
- ] }) : null
3300
- ]
3301
- }
3302
- );
3637
+ }
3638
+ );
3639
+ })(),
3640
+ model.nodes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", pointerEvents: "none", gap: 8 }, children: [
3641
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { fontSize: 36, opacity: 0.1, color: t.textPrimary }, children: variant === "question" ? "?" : variant === "journey" ? "\u2197" : "\u2B21" }),
3642
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { fontSize: 13, color: t.textMuted, fontWeight: 500 }, children: [
3643
+ "Click ",
3644
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("strong", { style: { color: acc.color }, children: [
3645
+ "+ ",
3646
+ variantLabel
3647
+ ] }),
3648
+ " to start"
3649
+ ] })
3650
+ ] }),
3651
+ model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3652
+ Minimap,
3653
+ {
3654
+ model,
3655
+ viewportW: viewport.w,
3656
+ viewportH: viewport.h,
3657
+ transform,
3658
+ isDark,
3659
+ accentColor: acc.color,
3660
+ measureNode: (n) => nodeDims(n, variant),
3661
+ onCenterOn: (cx, cy) => {
3662
+ setTransform((tr) => ({ ...tr, x: viewport.w / 2 - cx * tr.scale, y: viewport.h / 2 - cy * tr.scale }));
3663
+ }
3664
+ }
3665
+ ),
3666
+ ctxMenu && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3667
+ ContextMenu,
3668
+ {
3669
+ x: ctxMenu.x,
3670
+ y: ctxMenu.y,
3671
+ nodeId: ctxMenu.nodeId,
3672
+ edgeId: ctxMenu.edgeId,
3673
+ isDark,
3674
+ t,
3675
+ acc,
3676
+ canUndo: history.canUndo,
3677
+ canRedo: history.canRedo,
3678
+ onUndo: onCtxUndo,
3679
+ onRedo: onCtxRedo,
3680
+ onReCenter: onCtxReCenter,
3681
+ onAddNode: onCtxAddNode,
3682
+ onDuplicate: onCtxDuplicate,
3683
+ onRename: onCtxRename,
3684
+ onDelete: onCtxDelete,
3685
+ onDisconnect: onCtxDisconnect,
3686
+ currentEdgeStyle: ctxEdgeStyle,
3687
+ currentEdgeArrow: ctxEdgeArrow,
3688
+ edgeHasWaypoint: ctxEdgeHasWaypoint,
3689
+ onEdgeRename: onCtxEdgeRename,
3690
+ onEdgeStyle: onCtxEdgeStyle,
3691
+ onEdgeArrowhead: onCtxEdgeArrowhead,
3692
+ onEdgeDelete: onCtxEdgeDelete,
3693
+ onEdgeResetRouting: onCtxEdgeResetRouting,
3694
+ containerRef
3695
+ }
3696
+ )
3697
+ ] });
3303
3698
  }
3304
3699
 
3305
3700
  // src/ui/hooks/useHistory.ts
3306
- var import_react13 = require("react");
3701
+ var import_react15 = require("react");
3307
3702
  var MAX_HISTORY = 80;
3308
3703
  function useHistory(initial, onChange) {
3309
- const [state, setState] = (0, import_react13.useState)(initial);
3310
- const stackRef = (0, import_react13.useRef)([initial]);
3311
- const idxRef = (0, import_react13.useRef)(0);
3312
- const [, setTick] = (0, import_react13.useState)(0);
3704
+ const [state, setState] = (0, import_react15.useState)(initial);
3705
+ const stackRef = (0, import_react15.useRef)([initial]);
3706
+ const idxRef = (0, import_react15.useRef)(0);
3707
+ const [, setTick] = (0, import_react15.useState)(0);
3313
3708
  const bump = () => setTick((n) => n + 1);
3314
- const apply = (0, import_react13.useCallback)(
3709
+ const apply = (0, import_react15.useCallback)(
3315
3710
  (next) => {
3316
3711
  setState(next);
3317
3712
  onChange?.(next);
3318
3713
  },
3319
3714
  [onChange]
3320
3715
  );
3321
- const applyAndPush = (0, import_react13.useCallback)(
3716
+ const applyAndPush = (0, import_react15.useCallback)(
3322
3717
  (next) => {
3323
3718
  const stack = stackRef.current.slice(0, idxRef.current + 1);
3324
3719
  stack.push(next);
@@ -3331,7 +3726,7 @@ function useHistory(initial, onChange) {
3331
3726
  },
3332
3727
  [onChange]
3333
3728
  );
3334
- const undo = (0, import_react13.useCallback)(() => {
3729
+ const undo = (0, import_react15.useCallback)(() => {
3335
3730
  if (idxRef.current <= 0) return;
3336
3731
  idxRef.current--;
3337
3732
  const next = stackRef.current[idxRef.current];
@@ -3339,7 +3734,7 @@ function useHistory(initial, onChange) {
3339
3734
  onChange?.(next);
3340
3735
  bump();
3341
3736
  }, [onChange]);
3342
- const redo = (0, import_react13.useCallback)(() => {
3737
+ const redo = (0, import_react15.useCallback)(() => {
3343
3738
  if (idxRef.current >= stackRef.current.length - 1) return;
3344
3739
  idxRef.current++;
3345
3740
  const next = stackRef.current[idxRef.current];
@@ -3359,10 +3754,10 @@ function useHistory(initial, onChange) {
3359
3754
  }
3360
3755
 
3361
3756
  // src/ui/hooks/useCanvasWheel.ts
3362
- var import_react14 = require("react");
3757
+ var import_react16 = require("react");
3363
3758
  function useCanvasWheel(ref, setTransform, options = {}) {
3364
3759
  const { min = 0.15, max = 3, factor = 0.1 } = options;
3365
- (0, import_react14.useEffect)(() => {
3760
+ (0, import_react16.useEffect)(() => {
3366
3761
  const el = ref.current;
3367
3762
  if (!el) return;
3368
3763
  const onWheel = (e) => {
@@ -3386,7 +3781,7 @@ function useCanvasWheel(ref, setTransform, options = {}) {
3386
3781
  }
3387
3782
 
3388
3783
  // src/ui/hooks/useCanvasTouch.ts
3389
- var import_react15 = require("react");
3784
+ var import_react17 = require("react");
3390
3785
  function useCanvasTouch(ref, {
3391
3786
  transform,
3392
3787
  setTransform,
@@ -3396,7 +3791,7 @@ function useCanvasTouch(ref, {
3396
3791
  longPressMs = 550,
3397
3792
  longPressSlop = 8
3398
3793
  }) {
3399
- (0, import_react15.useEffect)(() => {
3794
+ (0, import_react17.useEffect)(() => {
3400
3795
  const el = ref.current;
3401
3796
  if (!el) return;
3402
3797
  let touchPan = null;
@@ -3500,10 +3895,10 @@ function useCanvasTouch(ref, {
3500
3895
  }
3501
3896
 
3502
3897
  // src/ui/hooks/useElementSize.ts
3503
- var import_react16 = require("react");
3898
+ var import_react18 = require("react");
3504
3899
  function useElementSize(ref) {
3505
- const [size, setSize] = (0, import_react16.useState)({ w: 0, h: 0 });
3506
- (0, import_react16.useEffect)(() => {
3900
+ const [size, setSize] = (0, import_react18.useState)({ w: 0, h: 0 });
3901
+ (0, import_react18.useEffect)(() => {
3507
3902
  const el = ref.current;
3508
3903
  if (!el || typeof ResizeObserver === "undefined") return;
3509
3904
  const measure = () => {
@@ -3609,14 +4004,12 @@ function nearestInDirection(fromX, fromY, dir, candidates) {
3609
4004
  }
3610
4005
 
3611
4006
  // src/ui/DiagramEditor.tsx
3612
- var import_jsx_runtime9 = require("react/jsx-runtime");
3613
- var STYLE_LABEL2 = { pointerEvents: "none", userSelect: "none" };
3614
- var STYLE_LIVE_PORT = { opacity: 0.85, pointerEvents: "none" };
4007
+ var import_jsx_runtime11 = require("react/jsx-runtime");
3615
4008
  var STYLE_SR_ONLY = { position: "absolute", width: 1, height: 1, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap", border: 0 };
3616
4009
  var STYLE_FLEX_ROW = { flex: 1, display: "flex", overflow: "hidden" };
3617
4010
  function DiagramEditor(props) {
3618
4011
  if (props.initialModel?.type === "sequence") {
3619
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4012
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3620
4013
  SequenceEditor,
3621
4014
  {
3622
4015
  initialModel: props.initialModel,
@@ -3630,7 +4023,7 @@ function DiagramEditor(props) {
3630
4023
  }
3631
4024
  );
3632
4025
  }
3633
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FlowchartEditor, { ...props });
4026
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(FlowchartEditor, { ...props });
3634
4027
  }
3635
4028
  function FlowchartEditor({
3636
4029
  initialModel,
@@ -3644,25 +4037,25 @@ function FlowchartEditor({
3644
4037
  themeOverrides
3645
4038
  }) {
3646
4039
  const base = initialModel ? { ...initialModel, variant: initialModel.variant ?? variant } : presetFlowchartModel(variant);
3647
- const notify = (0, import_react17.useCallback)((m) => onChange?.(m), [onChange]);
4040
+ const notify = (0, import_react19.useCallback)((m) => onChange?.(m), [onChange]);
3648
4041
  const history = useHistory(base, notify);
3649
4042
  const { state: model, apply: applyModel, applyAndPush, undo, redo } = history;
3650
- const [transform, setTransform] = (0, import_react17.useState)({ x: 60, y: 60, scale: 1 });
3651
- const [selected, setSelected] = (0, import_react17.useState)(null);
3652
- const [selectedSet, setSelectedSet] = (0, import_react17.useState)(() => /* @__PURE__ */ new Set());
3653
- const [drag, setDrag] = (0, import_react17.useState)(null);
3654
- const [pan, setPan] = (0, import_react17.useState)(null);
3655
- const [boxSel, setBoxSel] = (0, import_react17.useState)(null);
3656
- const [liveEdge, setLiveEdge] = (0, import_react17.useState)(null);
3657
- const [alignGuides, setAlignGuides] = (0, import_react17.useState)(null);
3658
- const [waypointDrag, setWaypointDrag] = (0, import_react17.useState)(null);
3659
- const groupDragOriginsRef = (0, import_react17.useRef)(null);
3660
- const clipboardRef = (0, import_react17.useRef)(null);
3661
- const selectOne = (0, import_react17.useCallback)((id) => {
4043
+ const [transform, setTransform] = (0, import_react19.useState)({ x: 60, y: 60, scale: 1 });
4044
+ const [selected, setSelected] = (0, import_react19.useState)(null);
4045
+ const [selectedSet, setSelectedSet] = (0, import_react19.useState)(() => /* @__PURE__ */ new Set());
4046
+ const [drag, setDrag] = (0, import_react19.useState)(null);
4047
+ const [pan, setPan] = (0, import_react19.useState)(null);
4048
+ const [boxSel, setBoxSel] = (0, import_react19.useState)(null);
4049
+ const [liveEdge, setLiveEdge] = (0, import_react19.useState)(null);
4050
+ const [alignGuides, setAlignGuides] = (0, import_react19.useState)(null);
4051
+ const [waypointDrag, setWaypointDrag] = (0, import_react19.useState)(null);
4052
+ const groupDragOriginsRef = (0, import_react19.useRef)(null);
4053
+ const clipboardRef = (0, import_react19.useRef)(null);
4054
+ const selectOne = (0, import_react19.useCallback)((id) => {
3662
4055
  setSelected(id);
3663
4056
  setSelectedSet(id ? /* @__PURE__ */ new Set([id]) : /* @__PURE__ */ new Set());
3664
4057
  }, []);
3665
- const toggleSelect = (0, import_react17.useCallback)((id) => {
4058
+ const toggleSelect = (0, import_react19.useCallback)((id) => {
3666
4059
  setSelectedSet((prev) => {
3667
4060
  const next = new Set(prev);
3668
4061
  if (next.has(id)) {
@@ -3676,26 +4069,26 @@ function FlowchartEditor({
3676
4069
  return next;
3677
4070
  });
3678
4071
  }, []);
3679
- const clearSelection = (0, import_react17.useCallback)(() => {
4072
+ const clearSelection = (0, import_react19.useCallback)(() => {
3680
4073
  setSelected(null);
3681
4074
  setSelectedSet(/* @__PURE__ */ new Set());
3682
4075
  }, []);
3683
- const [editingId, setEditingId] = (0, import_react17.useState)(null);
3684
- const [editLabel, setEditLabel] = (0, import_react17.useState)("");
3685
- const [editingEdgeId, setEditingEdgeId] = (0, import_react17.useState)(null);
3686
- const [editEdgeLabel, setEditEdgeLabel] = (0, import_react17.useState)("");
3687
- const [hoveredId, setHoveredId] = (0, import_react17.useState)(null);
3688
- const [ctxMenu, setCtxMenu] = (0, import_react17.useState)(null);
3689
- const [navOpen, setNavOpen] = (0, import_react17.useState)(true);
3690
- const [announcement, setAnnouncement] = (0, import_react17.useState)("");
3691
- const svgRef = (0, import_react17.useRef)(null);
3692
- const containerRef = (0, import_react17.useRef)(null);
4076
+ const [editingId, setEditingId] = (0, import_react19.useState)(null);
4077
+ const [editLabel, setEditLabel] = (0, import_react19.useState)("");
4078
+ const [editingEdgeId, setEditingEdgeId] = (0, import_react19.useState)(null);
4079
+ const [editEdgeLabel, setEditEdgeLabel] = (0, import_react19.useState)("");
4080
+ const [hoveredId, setHoveredId] = (0, import_react19.useState)(null);
4081
+ const [ctxMenu, setCtxMenu] = (0, import_react19.useState)(null);
4082
+ const [navOpen, setNavOpen] = (0, import_react19.useState)(true);
4083
+ const [announcement, setAnnouncement] = (0, import_react19.useState)("");
4084
+ const svgRef = (0, import_react19.useRef)(null);
4085
+ const containerRef = (0, import_react19.useRef)(null);
3693
4086
  const reducedMotion = usePrefersReducedMotion();
3694
4087
  const { t, isDark } = useEditorTheme(theme, themeOverrides, { light: lightTheme, dark: darkTheme });
3695
4088
  const isCoarse = useIsCoarsePointer();
3696
4089
  const portR = isCoarse ? 9 : 6;
3697
4090
  const viewport = useElementSize(svgRef);
3698
- const reCenter = (0, import_react17.useCallback)(() => {
4091
+ const reCenter = (0, import_react19.useCallback)(() => {
3699
4092
  if (!svgRef.current) return;
3700
4093
  const rect = svgRef.current.getBoundingClientRect();
3701
4094
  const W2 = rect.width, H2 = rect.height;
@@ -3719,7 +4112,7 @@ function FlowchartEditor({
3719
4112
  const cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;
3720
4113
  setTransform({ scale, x: W2 / 2 - cx * scale, y: H2 / 2 - cy * scale });
3721
4114
  }, [model.nodes, variant]);
3722
- const jumpToNode = (0, import_react17.useCallback)((nodeId) => {
4115
+ const jumpToNode = (0, import_react19.useCallback)((nodeId) => {
3723
4116
  const node = model.nodes.find((n) => n.id === nodeId);
3724
4117
  if (!node || !svgRef.current) return;
3725
4118
  const rect = svgRef.current.getBoundingClientRect();
@@ -3730,7 +4123,7 @@ function FlowchartEditor({
3730
4123
  setTransform({ scale, x: rect.width / 2 - cx * scale, y: rect.height / 2 - cy * scale });
3731
4124
  selectOne(nodeId);
3732
4125
  }, [model.nodes, variant, transform.scale, selectOne]);
3733
- const duplicateIds = (0, import_react17.useCallback)((ids) => {
4126
+ const duplicateIds = (0, import_react19.useCallback)((ids) => {
3734
4127
  if (ids.length === 0) return;
3735
4128
  const idSet = new Set(ids);
3736
4129
  const idMap = /* @__PURE__ */ new Map();
@@ -3762,92 +4155,90 @@ function FlowchartEditor({
3762
4155
  setSelected(newIds[newIds.length - 1] ?? null);
3763
4156
  setSelectedSet(new Set(newIds));
3764
4157
  }, [model, applyAndPush]);
3765
- const duplicateNode = (0, import_react17.useCallback)((nodeId) => {
4158
+ const duplicateNode = (0, import_react19.useCallback)((nodeId) => {
3766
4159
  duplicateIds([nodeId]);
3767
4160
  }, [duplicateIds]);
3768
- (0, import_react17.useEffect)(() => {
4161
+ (0, import_react19.useEffect)(() => {
3769
4162
  if (!ctxMenu) return;
3770
4163
  const close = () => setCtxMenu(null);
3771
4164
  window.addEventListener("mousedown", close);
3772
4165
  return () => window.removeEventListener("mousedown", close);
3773
4166
  }, [ctxMenu]);
3774
- (0, import_react17.useEffect)(() => {
3775
- const onKey = (e) => {
3776
- const tgt = e.target;
3777
- if (tgt && (tgt.tagName === "INPUT" || tgt.tagName === "TEXTAREA" || tgt.isContentEditable)) return;
3778
- const ctrl = e.ctrlKey || e.metaKey;
3779
- if (ctrl && e.key === "z") {
3780
- e.preventDefault();
3781
- undo();
3782
- return;
3783
- }
3784
- if (ctrl && (e.key === "y" || e.shiftKey && e.key === "z")) {
3785
- e.preventDefault();
3786
- redo();
3787
- return;
3788
- }
3789
- if (ctrl && e.key === "0") {
3790
- e.preventDefault();
3791
- reCenter();
3792
- return;
3793
- }
3794
- if (ctrl && (e.key === "d" || e.key === "D")) {
3795
- if (selectedSet.size > 0) {
3796
- e.preventDefault();
3797
- duplicateIds(Array.from(selectedSet));
3798
- }
3799
- return;
4167
+ const keyCommands = [
4168
+ { match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey, run: () => {
4169
+ undo();
4170
+ return true;
4171
+ } },
4172
+ { match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"), run: () => {
4173
+ redo();
4174
+ return true;
4175
+ } },
4176
+ { match: (e) => (e.ctrlKey || e.metaKey) && e.key === "0", run: () => {
4177
+ reCenter();
4178
+ return true;
4179
+ } },
4180
+ {
4181
+ match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "d" || e.key === "D") && selectedSet.size > 0,
4182
+ run: () => {
4183
+ duplicateIds(Array.from(selectedSet));
4184
+ return true;
3800
4185
  }
3801
- if (ctrl && (e.key === "c" || e.key === "C")) {
3802
- if (selectedSet.size > 0) {
3803
- e.preventDefault();
3804
- const ids = new Set(selectedSet);
3805
- const nodes = model.nodes.filter((n) => ids.has(n.id));
3806
- const edges = model.edges.filter((ed) => ids.has(ed.from) && ids.has(ed.to));
3807
- clipboardRef.current = {
3808
- nodes: nodes.map((n) => ({ ...n })),
3809
- edges: edges.map((ed) => ({ ...ed }))
3810
- };
3811
- }
3812
- return;
4186
+ },
4187
+ {
4188
+ match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "c" || e.key === "C") && selectedSet.size > 0,
4189
+ run: () => {
4190
+ const ids = new Set(selectedSet);
4191
+ const nodes = model.nodes.filter((n) => ids.has(n.id));
4192
+ const edges = model.edges.filter((ed) => ids.has(ed.from) && ids.has(ed.to));
4193
+ clipboardRef.current = {
4194
+ nodes: nodes.map((n) => ({ ...n })),
4195
+ edges: edges.map((ed) => ({ ...ed }))
4196
+ };
4197
+ return true;
3813
4198
  }
3814
- if (ctrl && (e.key === "v" || e.key === "V")) {
4199
+ },
4200
+ {
4201
+ match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "v" || e.key === "V"),
4202
+ run: () => {
3815
4203
  const clip = clipboardRef.current;
3816
- if (clip && clip.nodes.length > 0) {
3817
- e.preventDefault();
3818
- const idMap = /* @__PURE__ */ new Map();
3819
- const nextNode = makeIdSource("node", model.nodes);
3820
- const nextEdge = makeIdSource("e", model.edges);
3821
- const newNodes = clip.nodes.map((n) => {
3822
- const newId = nextNode();
3823
- idMap.set(n.id, newId);
3824
- return { ...n, id: newId, x: (n.x ?? 0) + 24, y: (n.y ?? 0) + 24 };
3825
- });
3826
- const newEdges = clip.edges.map((ed) => ({
3827
- ...ed,
3828
- id: nextEdge(),
3829
- from: idMap.get(ed.from) ?? ed.from,
3830
- to: idMap.get(ed.to) ?? ed.to
3831
- }));
3832
- const m = { ...model, nodes: [...model.nodes, ...newNodes], edges: [...model.edges, ...newEdges] };
3833
- applyAndPush(m);
3834
- const newIds = newNodes.map((n) => n.id);
3835
- setSelected(newIds[newIds.length - 1]);
3836
- setSelectedSet(new Set(newIds));
3837
- setAnnouncement(`Pasted ${newIds.length} ${variantLabel.toLowerCase()}${newIds.length === 1 ? "" : "s"}.`);
3838
- }
3839
- return;
4204
+ if (!clip || clip.nodes.length === 0) return false;
4205
+ const idMap = /* @__PURE__ */ new Map();
4206
+ const nextNode = makeIdSource("node", model.nodes);
4207
+ const nextEdge = makeIdSource("e", model.edges);
4208
+ const newNodes = clip.nodes.map((n) => {
4209
+ const newId = nextNode();
4210
+ idMap.set(n.id, newId);
4211
+ return { ...n, id: newId, x: (n.x ?? 0) + 24, y: (n.y ?? 0) + 24 };
4212
+ });
4213
+ const newEdges = clip.edges.map((ed) => ({
4214
+ ...ed,
4215
+ id: nextEdge(),
4216
+ from: idMap.get(ed.from) ?? ed.from,
4217
+ to: idMap.get(ed.to) ?? ed.to
4218
+ }));
4219
+ const m = { ...model, nodes: [...model.nodes, ...newNodes], edges: [...model.edges, ...newEdges] };
4220
+ applyAndPush(m);
4221
+ const newIds = newNodes.map((n) => n.id);
4222
+ setSelected(newIds[newIds.length - 1]);
4223
+ setSelectedSet(new Set(newIds));
4224
+ setAnnouncement(`Pasted ${newIds.length} ${variantLabel.toLowerCase()}${newIds.length === 1 ? "" : "s"}.`);
4225
+ return true;
3840
4226
  }
3841
- if (e.key === "Escape") {
4227
+ },
4228
+ {
4229
+ match: (e) => e.key === "Escape",
4230
+ run: () => {
3842
4231
  if (ctxMenu) setCtxMenu(null);
3843
4232
  if (liveEdge) setLiveEdge(null);
3844
4233
  if (editingId) setEditingId(null);
3845
4234
  if (boxSel) setBoxSel(null);
3846
4235
  if (selectedSet.size > 0) clearSelection();
3847
- return;
4236
+ return true;
3848
4237
  }
3849
- if ((e.key === "Delete" || e.key === "Backspace") && selectedSet.size > 0) {
3850
- e.preventDefault();
4238
+ },
4239
+ {
4240
+ match: (e) => (e.key === "Delete" || e.key === "Backspace") && selectedSet.size > 0,
4241
+ run: () => {
3851
4242
  const ids = new Set(selectedSet);
3852
4243
  const updated = {
3853
4244
  ...model,
@@ -3857,29 +4248,34 @@ function FlowchartEditor({
3857
4248
  applyAndPush(updated);
3858
4249
  clearSelection();
3859
4250
  setAnnouncement(`Deleted ${ids.size} ${variantLabel.toLowerCase()}${ids.size === 1 ? "" : "s"}.`);
3860
- return;
4251
+ return true;
3861
4252
  }
3862
- if (selectedSet.size > 0 && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight")) {
4253
+ },
4254
+ {
4255
+ match: (e) => selectedSet.size > 0 && e.altKey && !!selected && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight"),
4256
+ run: (e) => {
3863
4257
  const dirKey = e.key === "ArrowLeft" ? "left" : e.key === "ArrowRight" ? "right" : e.key === "ArrowUp" ? "up" : "down";
3864
- if (e.altKey && selected) {
3865
- e.preventDefault();
3866
- const origin = model.nodes.find((n) => n.id === selected);
3867
- if (!origin) return;
3868
- const od = nodeDims(origin, variant);
3869
- const ox = (origin.x ?? 0) + od.w / 2;
3870
- const oy = (origin.y ?? 0) + od.h / 2;
3871
- const candidates = model.nodes.filter((n) => n.id !== selected).map((n) => {
3872
- const d = nodeDims(n, variant);
3873
- return { id: n.id, x: (n.x ?? 0) + d.w / 2, y: (n.y ?? 0) + d.h / 2 };
3874
- });
3875
- const nextId2 = nearestInDirection(ox, oy, dirKey, candidates);
3876
- if (nextId2) {
3877
- selectOne(nextId2);
3878
- setAnnouncement(`Selected ${model.nodes.find((n) => n.id === nextId2)?.label ?? ""}.`);
3879
- }
3880
- return;
4258
+ const origin = model.nodes.find((n) => n.id === selected);
4259
+ if (!origin) return false;
4260
+ const od = nodeDims(origin, variant);
4261
+ const ox = (origin.x ?? 0) + od.w / 2;
4262
+ const oy = (origin.y ?? 0) + od.h / 2;
4263
+ const candidates = model.nodes.filter((n) => n.id !== selected).map((n) => {
4264
+ const d = nodeDims(n, variant);
4265
+ return { id: n.id, x: (n.x ?? 0) + d.w / 2, y: (n.y ?? 0) + d.h / 2 };
4266
+ });
4267
+ const nextNodeId = nearestInDirection(ox, oy, dirKey, candidates);
4268
+ if (nextNodeId) {
4269
+ selectOne(nextNodeId);
4270
+ setAnnouncement(`Selected ${model.nodes.find((n) => n.id === nextNodeId)?.label ?? ""}.`);
3881
4271
  }
3882
- e.preventDefault();
4272
+ return true;
4273
+ }
4274
+ },
4275
+ {
4276
+ match: (e) => selectedSet.size > 0 && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight"),
4277
+ run: (e) => {
4278
+ const dirKey = e.key === "ArrowLeft" ? "left" : e.key === "ArrowRight" ? "right" : e.key === "ArrowUp" ? "up" : "down";
3883
4279
  const step = e.shiftKey ? GRID * 4 : GRID;
3884
4280
  const dx = dirKey === "left" ? -step : dirKey === "right" ? step : 0;
3885
4281
  const dy = dirKey === "up" ? -step : dirKey === "down" ? step : 0;
@@ -3889,17 +4285,17 @@ function FlowchartEditor({
3889
4285
  nodes: model.nodes.map((n) => ids.has(n.id) ? { ...n, x: snap((n.x ?? 0) + dx), y: snap((n.y ?? 0) + dy) } : n)
3890
4286
  };
3891
4287
  applyAndPush(updated);
4288
+ return true;
3892
4289
  }
3893
- };
3894
- window.addEventListener("keydown", onKey);
3895
- return () => window.removeEventListener("keydown", onKey);
3896
- }, [undo, redo, reCenter, selected, selectedSet, ctxMenu, liveEdge, editingId, boxSel, model, applyAndPush, duplicateNode, clearSelection]);
3897
- const toCanvas = (0, import_react17.useCallback)((clientX, clientY) => {
4290
+ }
4291
+ ];
4292
+ useEditorKeyboard(keyCommands, [undo, redo, reCenter, selected, selectedSet, ctxMenu, liveEdge, editingId, boxSel, model, applyAndPush, duplicateNode, clearSelection]);
4293
+ const toCanvas = (0, import_react19.useCallback)((clientX, clientY) => {
3898
4294
  const rect = svgRef.current.getBoundingClientRect();
3899
4295
  return { x: (clientX - rect.left - transform.x) / transform.scale, y: (clientY - rect.top - transform.y) / transform.scale };
3900
4296
  }, [transform]);
3901
4297
  useCanvasWheel(svgRef, setTransform);
3902
- const onCanvasLongPress = (0, import_react17.useCallback)((x, y) => {
4298
+ const onCanvasLongPress = (0, import_react19.useCallback)((x, y) => {
3903
4299
  setCtxMenu({ x, y, nodeId: null });
3904
4300
  }, []);
3905
4301
  useCanvasTouch(svgRef, { transform, setTransform, onLongPress: onCanvasLongPress });
@@ -4171,7 +4567,7 @@ function FlowchartEditor({
4171
4567
  applyAndPush(updated);
4172
4568
  };
4173
4569
  const handleExport = useExporters(model, onExport, "diagram");
4174
- const positionFlowchartNodes = (0, import_react17.useCallback)((m) => ({
4570
+ const positionFlowchartNodes = (0, import_react19.useCallback)((m) => ({
4175
4571
  ...m,
4176
4572
  nodes: m.nodes.map((n, i) => ({
4177
4573
  ...n,
@@ -4182,11 +4578,11 @@ function FlowchartEditor({
4182
4578
  const handleImport = useImporter(applyAndPush, { transform: positionFlowchartNodes });
4183
4579
  const acc = variantAccent(variant, isDark);
4184
4580
  const variantLabel = variant === "question" ? "Question" : variant === "journey" ? "Step" : "Node";
4185
- const shadowColor = isDark ? "rgba(0,0,0,0.55)" : "rgba(15,23,42,0.09)";
4186
- const arrowColor = isDark ? "#64748b" : "#94a3b8";
4581
+ const shadowClr = shadowColor(isDark);
4582
+ const arrowClr = arrowColor(isDark);
4187
4583
  const amberArrow = isDark ? ACCENT.amberDark : ACCENT.amber;
4188
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "fsd-editor", style: { display: "flex", flexDirection: "column", height, width: "100%", fontFamily: "ui-sans-serif,system-ui,sans-serif", boxSizing: "border-box", background: t.ctrlsBg }, children: [
4189
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("style", { children: `
4584
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "fsd-editor", style: { display: "flex", flexDirection: "column", height, width: "100%", fontFamily: "ui-sans-serif,system-ui,sans-serif", boxSizing: "border-box", background: t.ctrlsBg }, children: [
4585
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: `
4190
4586
  .fsd-editor button:focus-visible,
4191
4587
  .fsd-editor input:focus-visible,
4192
4588
  .fsd-editor textarea:focus-visible,
@@ -4201,7 +4597,7 @@ function FlowchartEditor({
4201
4597
  outline-offset: -2px;
4202
4598
  }
4203
4599
  ` }),
4204
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4600
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4205
4601
  "div",
4206
4602
  {
4207
4603
  role: "status",
@@ -4211,28 +4607,28 @@ function FlowchartEditor({
4211
4607
  children: announcement
4212
4608
  }
4213
4609
  ),
4214
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
4215
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: 6, padding: "7px 14px", background: t.ctrlsBg, borderBottom: `1px solid ${t.ctrlsBorder}`, alignItems: "center", flexWrap: "wrap" }, children: [
4216
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("button", { onClick: () => addNode(), style: ctrlBtn(acc.color, isDark), children: [
4610
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
4611
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "flex", gap: 6, padding: "7px 14px", background: t.ctrlsBg, borderBottom: `1px solid ${t.ctrlsBorder}`, alignItems: "center", flexWrap: "wrap" }, children: [
4612
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { onClick: () => addNode(), style: ctrlBtn(acc.color, isDark), children: [
4217
4613
  "+ ",
4218
4614
  variantLabel
4219
4615
  ] }),
4220
- selectedSet.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
4221
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { width: 1, height: 20, background: t.ctrlsBorder, margin: "0 2px" } }),
4222
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: deleteSelected, style: { ...ctrlBtn("transparent", isDark), color: "#ef4444", border: `1px solid ${isDark ? "#7f1d1d" : "#fca5a5"}` }, children: selectedSet.size > 1 ? `Delete (${selectedSet.size})` : "Delete" })
4616
+ selectedSet.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
4617
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { width: 1, height: 20, background: t.ctrlsBorder, margin: "0 2px" } }),
4618
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { onClick: deleteSelected, style: { ...ctrlBtn("transparent", isDark), color: "#ef4444", border: `1px solid ${isDark ? "#7f1d1d" : "#fca5a5"}` }, children: selectedSet.size > 1 ? `Delete (${selectedSet.size})` : "Delete" })
4223
4619
  ] }),
4224
- liveEdge && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { fontSize: 11, color: acc.color, fontWeight: 600, marginLeft: 6 }, children: [
4620
+ liveEdge && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: { fontSize: 11, color: acc.color, fontWeight: 600, marginLeft: 6 }, children: [
4225
4621
  liveEdge.answerLabel ? `Routing "${liveEdge.answerLabel}" \u2192` : "Drop on a node to connect",
4226
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontWeight: 400, color: t.textMuted, marginLeft: 6 }, children: "release to cancel" })
4622
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontWeight: 400, color: t.textMuted, marginLeft: 6 }, children: "release to cancel" })
4227
4623
  ] }),
4228
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
4624
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
4229
4625
  variant === "question" ? "drag answer port to connect \xB7 " : "drag port dot \xB7 ",
4230
4626
  "scroll to zoom \xB7 drag to pan"
4231
4627
  ] })
4232
4628
  ] }),
4233
- variant !== "flowchart" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "3px 14px", background: acc.fill, borderBottom: `1px solid ${acc.border}`, fontSize: 11, color: acc.color, fontWeight: 600 }, children: variant === "question" ? "? Question Flow \u2014 add answers in the panel, drag their port to connect" : "\u2197 Journey Map \u2014 numbered steps, drag port to sequence" }),
4234
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: STYLE_FLEX_ROW, children: [
4235
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4629
+ variant !== "flowchart" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { padding: "3px 14px", background: acc.fill, borderBottom: `1px solid ${acc.border}`, fontSize: 11, color: acc.color, fontWeight: 600 }, children: variant === "question" ? "? Question Flow \u2014 add answers in the panel, drag their port to connect" : "\u2197 Journey Map \u2014 numbered steps, drag port to sequence" }),
4630
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: STYLE_FLEX_ROW, children: [
4631
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4236
4632
  NodeNavigator,
4237
4633
  {
4238
4634
  model,
@@ -4246,323 +4642,162 @@ function FlowchartEditor({
4246
4642
  onSelect: jumpToNode
4247
4643
  }
4248
4644
  ),
4249
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { ref: containerRef, style: { flex: 1, overflow: "hidden", position: "relative", background: t.canvas }, children: [
4250
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4251
- "svg",
4252
- {
4253
- ref: svgRef,
4254
- width: "100%",
4255
- height: "100%",
4256
- role: "application",
4257
- "aria-label": `${variantLabel} diagram editor. ${model.nodes.length} ${variantLabel.toLowerCase()}s, ${model.edges.length} connections. Scroll to zoom, drag to pan, click a ${variantLabel.toLowerCase()} to select.`,
4258
- tabIndex: 0,
4259
- style: { display: "block", cursor: pan ? "grabbing" : drag ? "grabbing" : liveEdge ? "crosshair" : "default", userSelect: "none", outline: "none" },
4260
- onMouseDown: onSvgMouseDown,
4261
- onMouseMove,
4262
- onMouseUp,
4263
- onMouseLeave: onMouseUp,
4264
- onContextMenu: onSvgContextMenu,
4265
- children: [
4266
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("defs", { children: [
4267
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("style", { children: reducedMotion ? `
4268
- .edge-flow { stroke-dasharray: 0; }
4269
- .edge-flow-amber { stroke-dasharray: 0; }
4270
- .edge-live { stroke-dasharray: 4 4; }
4271
- ` : `
4272
- @keyframes edgeFlow { to { stroke-dashoffset: -13; } }
4273
- @keyframes edgeFlowFast { to { stroke-dashoffset: -13; } }
4274
- .edge-flow { stroke-dasharray: 8 5; animation: edgeFlow 0.9s linear infinite; }
4275
- .edge-flow-amber { stroke-dasharray: 6 4; animation: edgeFlowFast 0.65s linear infinite; }
4276
- .edge-live { stroke-dasharray: 7 5; animation: edgeFlow 0.55s linear infinite; }
4277
- ` }),
4278
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pattern", { id: "dots", width: GRID, height: GRID, patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: GRID / 2, cy: GRID / 2, r: 1.1, fill: t.dot }) }),
4279
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("filter", { id: "nodeShadow", x: "-25%", y: "-25%", width: "150%", height: "160%", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("feDropShadow", { dx: "0", dy: "3", stdDeviation: "5", floodColor: shadowColor, floodOpacity: "1" }) }),
4280
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("marker", { id: "arrowhead", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: arrowColor }) }),
4281
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("marker", { id: "arrowAmber", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: amberArrow }) }),
4282
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("marker", { id: "arrowLive", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: acc.color }) })
4283
- ] }),
4284
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("rect", { width: "100%", height: "100%", fill: "url(#dots)", "data-bg": "1" }),
4285
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("g", { transform: `translate(${transform.x},${transform.y}) scale(${transform.scale})`, children: [
4286
- model.edges.map((e) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4287
- EdgeLine,
4288
- {
4289
- edge: e,
4290
- nodes: model.nodes,
4291
- variant,
4292
- t,
4293
- isDark,
4294
- acc,
4295
- editing: editingEdgeId === e.id,
4296
- editValue: editEdgeLabel,
4297
- onEditChange: setEditEdgeLabel,
4298
- onEditCommit: commitEdgeEdit,
4299
- onEditCancel: () => setEditingEdgeId(null),
4300
- onDoubleClick: beginEditEdge,
4301
- onContextMenu: onEdgeContextMenu,
4302
- onWaypointDown: (ev, edgeId) => setWaypointDrag(edgeId)
4303
- },
4304
- e.id
4305
- )),
4306
- liveEdge && (() => {
4307
- const d = bezierPath2(liveEdge.fromX, liveEdge.fromY, liveEdge.toX, liveEdge.toY, liveEdge.exitDir);
4308
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d, fill: "none", stroke: acc.color, strokeWidth: 2, strokeLinecap: "round", className: "edge-live", opacity: 0.8, markerEnd: "url(#arrowLive)" });
4309
- })(),
4310
- alignGuides?.x && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4311
- "line",
4312
- {
4313
- x1: alignGuides.x.pos,
4314
- x2: alignGuides.x.pos,
4315
- y1: alignGuides.x.minY,
4316
- y2: alignGuides.x.maxY,
4317
- stroke: acc.color,
4318
- strokeWidth: 1 / transform.scale,
4319
- strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
4320
- opacity: 0.85,
4321
- pointerEvents: "none"
4322
- }
4323
- ),
4324
- alignGuides?.y && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4325
- "line",
4326
- {
4327
- y1: alignGuides.y.pos,
4328
- y2: alignGuides.y.pos,
4329
- x1: alignGuides.y.minX,
4330
- x2: alignGuides.y.maxX,
4331
- stroke: acc.color,
4332
- strokeWidth: 1 / transform.scale,
4333
- strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
4334
- opacity: 0.85,
4335
- pointerEvents: "none"
4336
- }
4337
- ),
4338
- model.nodes.map((node, idx) => {
4339
- const isHovered = hoveredId === node.id;
4340
- const isQuestion2 = variant === "question";
4341
- const { w: nW, h: nH } = nodeDims(node, variant);
4342
- const isSelected = selectedSet.has(node.id);
4343
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4344
- "g",
4345
- {
4346
- transform: `translate(${node.x ?? 0},${node.y ?? 0})`,
4347
- role: "button",
4348
- "aria-label": `${variantLabel} ${variant === "journey" ? idx + 1 + ": " : ""}${node.label}${isSelected ? ", selected" : ""}`,
4349
- style: { cursor: drag?.nodeId === node.id ? "grabbing" : "grab" },
4350
- onMouseDown: (e) => onNodeMouseDown(e, node.id),
4351
- onMouseUp: (e) => onNodeMouseUp(e, node.id),
4352
- onDoubleClick: (e) => onNodeDblClick(e, node.id),
4353
- onContextMenu: (e) => onNodeContextMenu(e, node.id),
4354
- onMouseEnter: () => setHoveredId(node.id),
4355
- onMouseLeave: () => setHoveredId(null),
4356
- children: [
4357
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("title", { children: `${variantLabel}: ${node.label}` }),
4358
- isQuestion2 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(QuestionNode, { node, selected: isSelected, edges: model.edges, isDark, onAnswerPortDown, qW: nW }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
4359
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NodeShape, { node, selected: isSelected, variant, stepNumber: variant === "journey" ? idx + 1 : void 0, t, isDark, w: nW }),
4360
- editingId === node.id ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("foreignObject", { x: 6, y: 6, width: nW - 12, height: NODE_H2 - 12, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4361
- "input",
4362
- {
4363
- autoFocus: true,
4364
- value: editLabel,
4365
- onChange: (e) => setEditLabel(e.target.value),
4366
- onBlur: commitEdit,
4367
- onKeyDown: (e) => {
4368
- if (e.key === "Enter") commitEdit();
4369
- if (e.key === "Escape") setEditingId(null);
4370
- },
4371
- style: { width: "100%", height: "100%", border: "none", borderRadius: 6, outline: `2px solid ${acc.color}`, textAlign: "center", fontSize: 13, fontWeight: 500, background: t.inputBg, boxSizing: "border-box", padding: "0 6px", fontFamily: "inherit", color: t.inputText }
4372
- }
4373
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("text", { x: nW / 2, y: NODE_H2 / 2 + 5, textAnchor: "middle", fontSize: 13, fontWeight: "500", fontFamily: "ui-sans-serif,system-ui,sans-serif", fill: isSelected ? acc.color : t.textPrimary, style: STYLE_LABEL2, children: node.label }),
4374
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4375
- "circle",
4376
- {
4377
- cx: nW / 2,
4378
- cy: NODE_H2 + 1,
4379
- r: portR,
4380
- fill: acc.color,
4381
- stroke: isDark ? "#0f172a" : "white",
4382
- strokeWidth: 2,
4383
- style: { cursor: "crosshair", opacity: isHovered || isCoarse ? 1 : 0, transition: "opacity 0.15s", pointerEvents: isHovered || isCoarse ? "all" : "none", filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))" },
4384
- onMouseDown: (e) => onPortMouseDown(e, node.id)
4385
- }
4386
- )
4387
- ] }),
4388
- liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: nW / 2, cy: -1, r: portR, fill: acc.color, stroke: isDark ? "#0f172a" : "white", strokeWidth: 2, style: STYLE_LIVE_PORT })
4389
- ]
4390
- },
4391
- node.id
4392
- );
4393
- })
4394
- ] })
4395
- ]
4396
- }
4397
- ),
4398
- boxSel && Math.abs(boxSel.cx - boxSel.sx) + Math.abs(boxSel.cy - boxSel.sy) > 4 && containerRef.current && (() => {
4399
- const rect = containerRef.current.getBoundingClientRect();
4400
- const left = Math.min(boxSel.sx, boxSel.cx) - rect.left;
4401
- const top = Math.min(boxSel.sy, boxSel.cy) - rect.top;
4402
- const w = Math.abs(boxSel.cx - boxSel.sx);
4403
- const h = Math.abs(boxSel.cy - boxSel.sy);
4404
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4405
- "div",
4406
- {
4407
- style: {
4408
- position: "absolute",
4409
- left,
4410
- top,
4411
- width: w,
4412
- height: h,
4413
- border: `1px dashed ${acc.color}`,
4414
- background: isDark ? "rgba(99,102,241,0.10)" : "rgba(99,102,241,0.08)",
4415
- pointerEvents: "none",
4416
- borderRadius: 4
4417
- }
4645
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4646
+ DiagramCanvas,
4647
+ {
4648
+ model,
4649
+ variant,
4650
+ variantLabel,
4651
+ t,
4652
+ isDark,
4653
+ acc,
4654
+ transform,
4655
+ setTransform,
4656
+ selected,
4657
+ selectedSet,
4658
+ hoveredId,
4659
+ setHoveredId,
4660
+ drag,
4661
+ pan,
4662
+ liveEdge,
4663
+ boxSel,
4664
+ alignGuides,
4665
+ editingEdgeId,
4666
+ editEdgeLabel,
4667
+ setEditEdgeLabel,
4668
+ commitEdgeEdit,
4669
+ setEditingEdgeId,
4670
+ beginEditEdge,
4671
+ onEdgeContextMenu,
4672
+ setWaypointDrag,
4673
+ editingId,
4674
+ editLabel,
4675
+ setEditLabel,
4676
+ commitEdit,
4677
+ setEditingId,
4678
+ onNodeMouseDown,
4679
+ onNodeMouseUp,
4680
+ onNodeDblClick,
4681
+ onNodeContextMenu,
4682
+ onPortMouseDown,
4683
+ onAnswerPortDown,
4684
+ onSvgMouseDown,
4685
+ onMouseMove,
4686
+ onMouseUp,
4687
+ onSvgContextMenu,
4688
+ reducedMotion,
4689
+ isCoarse,
4690
+ portR,
4691
+ shadowClr,
4692
+ arrowClr,
4693
+ amberArrow,
4694
+ viewport,
4695
+ svgRef,
4696
+ containerRef,
4697
+ ctxMenu,
4698
+ history,
4699
+ ctxEdgeStyle: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.style ?? "solid",
4700
+ ctxEdgeArrow: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.arrowhead ?? "arrow",
4701
+ ctxEdgeHasWaypoint: !!(ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.waypoint,
4702
+ onCtxUndo: () => {
4703
+ undo();
4704
+ setCtxMenu(null);
4705
+ },
4706
+ onCtxRedo: () => {
4707
+ redo();
4708
+ setCtxMenu(null);
4709
+ },
4710
+ onCtxReCenter: () => {
4711
+ reCenter();
4712
+ setCtxMenu(null);
4713
+ },
4714
+ onCtxAddNode: () => {
4715
+ const rect = svgRef.current.getBoundingClientRect();
4716
+ const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
4717
+ const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
4718
+ addNode({ x: cx, y: cy });
4719
+ setCtxMenu(null);
4720
+ },
4721
+ onCtxDuplicate: () => {
4722
+ if (ctxMenu?.nodeId) {
4723
+ duplicateNode(ctxMenu.nodeId);
4724
+ setCtxMenu(null);
4418
4725
  }
4419
- );
4420
- })(),
4421
- model.nodes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", pointerEvents: "none", gap: 8 }, children: [
4422
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: 36, opacity: 0.1, color: t.textPrimary }, children: variant === "question" ? "?" : variant === "journey" ? "\u2197" : "\u2B21" }),
4423
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { fontSize: 13, color: t.textMuted, fontWeight: 500 }, children: [
4424
- "Click ",
4425
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("strong", { style: { color: acc.color }, children: [
4426
- "+ ",
4427
- variantLabel
4428
- ] }),
4429
- " to start"
4430
- ] })
4431
- ] }),
4432
- model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4433
- Minimap,
4434
- {
4435
- model,
4436
- viewportW: viewport.w,
4437
- viewportH: viewport.h,
4438
- transform,
4439
- isDark,
4440
- accentColor: acc.color,
4441
- measureNode: (n) => nodeDims(n, variant),
4442
- onCenterOn: (cx, cy) => {
4443
- setTransform((tr) => ({ ...tr, x: viewport.w / 2 - cx * tr.scale, y: viewport.h / 2 - cy * tr.scale }));
4726
+ },
4727
+ onCtxRename: () => {
4728
+ if (ctxMenu?.nodeId) {
4729
+ const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
4730
+ setEditingId(ctxMenu.nodeId);
4731
+ setEditLabel(node.label);
4732
+ setCtxMenu(null);
4444
4733
  }
4445
- }
4446
- ),
4447
- ctxMenu && (() => {
4448
- const ctxEdge = ctxMenu.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0;
4449
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4450
- ContextMenu,
4451
- {
4452
- x: ctxMenu.x,
4453
- y: ctxMenu.y,
4454
- nodeId: ctxMenu.nodeId,
4455
- edgeId: ctxMenu.edgeId,
4456
- isDark,
4457
- t,
4458
- acc,
4459
- canUndo: history.canUndo,
4460
- canRedo: history.canRedo,
4461
- onUndo: () => {
4462
- undo();
4463
- setCtxMenu(null);
4464
- },
4465
- onRedo: () => {
4466
- redo();
4467
- setCtxMenu(null);
4468
- },
4469
- onReCenter: () => {
4470
- reCenter();
4471
- setCtxMenu(null);
4472
- },
4473
- onAddNode: () => {
4474
- const rect = svgRef.current.getBoundingClientRect();
4475
- const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
4476
- const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
4477
- addNode({ x: cx, y: cy });
4478
- setCtxMenu(null);
4479
- },
4480
- onDuplicate: () => {
4481
- if (ctxMenu.nodeId) {
4482
- duplicateNode(ctxMenu.nodeId);
4483
- setCtxMenu(null);
4484
- }
4485
- },
4486
- onRename: () => {
4487
- if (ctxMenu.nodeId) {
4488
- const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
4489
- setEditingId(ctxMenu.nodeId);
4490
- setEditLabel(node.label);
4491
- setCtxMenu(null);
4492
- }
4493
- },
4494
- onDelete: () => {
4495
- if (ctxMenu.nodeId) {
4496
- deleteNode(ctxMenu.nodeId);
4497
- setCtxMenu(null);
4498
- }
4499
- },
4500
- onDisconnect: () => {
4501
- if (ctxMenu.nodeId) {
4502
- const m = { ...model, edges: model.edges.filter((e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId) };
4503
- applyAndPush(m);
4504
- setCtxMenu(null);
4505
- }
4506
- },
4507
- currentEdgeStyle: ctxEdge?.style ?? "solid",
4508
- currentEdgeArrow: ctxEdge?.arrowhead ?? "arrow",
4509
- edgeHasWaypoint: !!ctxEdge?.waypoint,
4510
- onEdgeRename: () => {
4511
- if (ctxMenu.edgeId) {
4512
- beginEditEdge(ctxMenu.edgeId);
4513
- setCtxMenu(null);
4514
- }
4515
- },
4516
- onEdgeStyle: (s2) => {
4517
- if (ctxMenu.edgeId) {
4518
- setEdgeStyle(ctxMenu.edgeId, s2);
4519
- setCtxMenu(null);
4520
- }
4521
- },
4522
- onEdgeArrowhead: (a) => {
4523
- if (ctxMenu.edgeId) {
4524
- setEdgeArrowhead(ctxMenu.edgeId, a);
4525
- setCtxMenu(null);
4526
- }
4527
- },
4528
- onEdgeDelete: () => {
4529
- if (ctxMenu.edgeId) {
4530
- deleteEdge(ctxMenu.edgeId);
4531
- setCtxMenu(null);
4532
- }
4533
- },
4534
- onEdgeResetRouting: () => {
4535
- if (ctxMenu.edgeId) {
4536
- resetEdgeRouting(ctxMenu.edgeId);
4537
- setCtxMenu(null);
4538
- }
4539
- },
4540
- containerRef
4734
+ },
4735
+ onCtxDelete: () => {
4736
+ if (ctxMenu?.nodeId) {
4737
+ deleteNode(ctxMenu.nodeId);
4738
+ setCtxMenu(null);
4541
4739
  }
4542
- );
4543
- })()
4544
- ] }),
4545
- selected && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StepEditor, { nodeId: selected, model, onModelChange: (m) => {
4740
+ },
4741
+ onCtxDisconnect: () => {
4742
+ if (ctxMenu?.nodeId) {
4743
+ const m = { ...model, edges: model.edges.filter((e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId) };
4744
+ applyAndPush(m);
4745
+ setCtxMenu(null);
4746
+ }
4747
+ },
4748
+ onCtxEdgeRename: () => {
4749
+ if (ctxMenu?.edgeId) {
4750
+ beginEditEdge(ctxMenu.edgeId);
4751
+ setCtxMenu(null);
4752
+ }
4753
+ },
4754
+ onCtxEdgeStyle: (s2) => {
4755
+ if (ctxMenu?.edgeId) {
4756
+ setEdgeStyle(ctxMenu.edgeId, s2);
4757
+ setCtxMenu(null);
4758
+ }
4759
+ },
4760
+ onCtxEdgeArrowhead: (a) => {
4761
+ if (ctxMenu?.edgeId) {
4762
+ setEdgeArrowhead(ctxMenu.edgeId, a);
4763
+ setCtxMenu(null);
4764
+ }
4765
+ },
4766
+ onCtxEdgeDelete: () => {
4767
+ if (ctxMenu?.edgeId) {
4768
+ deleteEdge(ctxMenu.edgeId);
4769
+ setCtxMenu(null);
4770
+ }
4771
+ },
4772
+ onCtxEdgeResetRouting: () => {
4773
+ if (ctxMenu?.edgeId) {
4774
+ resetEdgeRouting(ctxMenu.edgeId);
4775
+ setCtxMenu(null);
4776
+ }
4777
+ }
4778
+ }
4779
+ ),
4780
+ selected && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StepEditor, { nodeId: selected, model, onModelChange: (m) => {
4546
4781
  applyAndPush(m);
4547
4782
  }, variant, isDark, t, acc }, selected)
4548
4783
  ] }),
4549
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { padding: "4px 14px", fontSize: 11, color: t.textMuted, background: t.statusBg, borderTop: `1px solid ${t.ctrlsBorder}`, display: "flex", gap: 16 }, children: [
4550
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
4784
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { padding: "4px 14px", fontSize: 11, color: t.textMuted, background: t.statusBg, borderTop: `1px solid ${t.ctrlsBorder}`, display: "flex", gap: 16 }, children: [
4785
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
4551
4786
  model.nodes.length,
4552
4787
  " ",
4553
4788
  variantLabel.toLowerCase(),
4554
4789
  "s"
4555
4790
  ] }),
4556
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
4791
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
4557
4792
  model.edges.length,
4558
4793
  " connections"
4559
4794
  ] }),
4560
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
4795
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
4561
4796
  Math.round(transform.scale * 100),
4562
4797
  "% zoom"
4563
4798
  ] }),
4564
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { marginLeft: "auto" }, children: "Ctrl+Z undo \xB7 Ctrl+Y redo \xB7 Ctrl+0 fit \xB7 Alt+Arrow traverse" }),
4565
- selected && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: acc.color }, children: model.nodes.find((n) => n.id === selected)?.label })
4799
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { marginLeft: "auto" }, children: "Ctrl+Z undo \xB7 Ctrl+Y redo \xB7 Ctrl+0 fit \xB7 Alt+Arrow traverse" }),
4800
+ selected && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { color: acc.color }, children: model.nodes.find((n) => n.id === selected)?.label })
4566
4801
  ] })
4567
4802
  ] });
4568
4803
  }