flowchart-sequence-designer 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +68 -8
- package/dist/ui/index.cjs +420 -274
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +398 -252
- package/dist/ui/index.js.map +1 -1
- package/package.json +4 -1
package/dist/ui/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/ui/DiagramEditor.tsx
|
|
2
|
-
import { useCallback as
|
|
2
|
+
import { useCallback as useCallback8, useEffect as useEffect10, useRef as useRef7, useState as useState12 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/ui/Toolbar.tsx
|
|
5
5
|
import { useState as useState2 } from "react";
|
|
@@ -412,10 +412,10 @@ function Toolbar({ onExport, onImport, allowedExports, allowImport = true }) {
|
|
|
412
412
|
] }),
|
|
413
413
|
/* @__PURE__ */ jsx2("div", { style: divider }),
|
|
414
414
|
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [
|
|
415
|
-
allowImport && onImport && /* @__PURE__ */ jsx2("button", { onClick: () => setImportOpen(true), style: ghostBtn, children: "\u2191 Import" }),
|
|
415
|
+
allowImport && onImport && /* @__PURE__ */ jsx2("button", { onClick: () => setImportOpen(true), "aria-label": "Import diagram", style: ghostBtn, children: "\u2191 Import" }),
|
|
416
416
|
formats.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
417
417
|
/* @__PURE__ */ jsx2("span", { style: { fontSize: 11, color: darkTheme.inputText, margin: "0 4px" }, children: "Export \u2192" }),
|
|
418
|
-
formats.map((f) => /* @__PURE__ */ jsx2("button", { onClick: () => onExport(f.key), style: exportBtn, children: f.label }, f.key))
|
|
418
|
+
formats.map((f) => /* @__PURE__ */ jsx2("button", { onClick: () => onExport(f.key), "aria-label": `Export as ${f.label}`, style: exportBtn, children: f.label }, f.key))
|
|
419
419
|
] })
|
|
420
420
|
] }),
|
|
421
421
|
onImport && /* @__PURE__ */ jsx2(
|
|
@@ -693,7 +693,7 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
693
693
|
/* @__PURE__ */ jsx3("label", { style: { display: "block", fontSize: 10, fontWeight: 700, color: tt.labelText, marginBottom: 8, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Shape" }),
|
|
694
694
|
/* @__PURE__ */ jsx3("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }, children: SHAPES.map((s2) => {
|
|
695
695
|
const active = (node.shape ?? "rectangle") === s2.key;
|
|
696
|
-
return /* @__PURE__ */ jsxs3("button", { onClick: () => setShape(s2.key), style: {
|
|
696
|
+
return /* @__PURE__ */ jsxs3("button", { onClick: () => setShape(s2.key), "aria-pressed": active, style: {
|
|
697
697
|
display: "flex",
|
|
698
698
|
flexDirection: "column",
|
|
699
699
|
alignItems: "center",
|
|
@@ -740,7 +740,12 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
740
740
|
/* @__PURE__ */ jsx3("button", { onClick: () => removeAnswer(ans), style: { background: "none", border: "none", color: tt.textMuted, cursor: "pointer", fontSize: 12, padding: "8px 10px", flexShrink: 0 }, title: "Remove", children: "\u2715" })
|
|
741
741
|
] }, ans + i);
|
|
742
742
|
}),
|
|
743
|
-
addingAnswer ? /* @__PURE__ */ jsxs3("div", {
|
|
743
|
+
addingAnswer ? /* @__PURE__ */ jsxs3("div", { role: "group", "aria-label": "Add answer form", onKeyDown: (e) => {
|
|
744
|
+
if (e.key === "Escape") {
|
|
745
|
+
setAddingAnswer(false);
|
|
746
|
+
setNewAnswer("");
|
|
747
|
+
}
|
|
748
|
+
}, style: { marginTop: 10, background: tt.addFormBg, borderRadius: 10, padding: 12, border: `1.5px solid ${accentBorder}` }, children: [
|
|
744
749
|
/* @__PURE__ */ jsx3("input", { autoFocus: true, value: newAnswer, onChange: (e) => setNewAnswer(e.target.value), onKeyDown: (e) => e.key === "Enter" && addAnswer(), placeholder: "Answer text\u2026", style: { ...inputStyle, marginBottom: 8 } }),
|
|
745
750
|
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 6 }, children: [
|
|
746
751
|
/* @__PURE__ */ jsx3("button", { onClick: addAnswer, style: addBtnStyle, children: "Add Answer" }),
|
|
@@ -778,7 +783,9 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
778
783
|
/* @__PURE__ */ jsx3("button", { onClick: () => removeEdge(edge.id), style: { background: "none", border: "none", color: tt.textMuted, cursor: "pointer", fontSize: 12, padding: "8px 10px", flexShrink: 0 }, title: "Remove", children: "\u2715" })
|
|
779
784
|
] }, edge.id);
|
|
780
785
|
}),
|
|
781
|
-
addingBranch ? /* @__PURE__ */ jsxs3("div", {
|
|
786
|
+
addingBranch ? /* @__PURE__ */ jsxs3("div", { role: "group", "aria-label": "Add branch form", onKeyDown: (e) => {
|
|
787
|
+
if (e.key === "Escape") setAddingBranch(false);
|
|
788
|
+
}, style: { marginTop: 10, background: tt.addFormBg, borderRadius: 10, padding: 12, border: `1.5px solid ${accentBorder}` }, children: [
|
|
782
789
|
/* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: 6, marginBottom: 10 }, children: ["new", "existing"].map((mode) => /* @__PURE__ */ jsx3("button", { onClick: () => setBranchMode(mode), style: {
|
|
783
790
|
flex: 1,
|
|
784
791
|
padding: "5px 0",
|
|
@@ -813,7 +820,7 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
813
820
|
}
|
|
814
821
|
|
|
815
822
|
// src/ui/SequenceEditor.tsx
|
|
816
|
-
import { useCallback as
|
|
823
|
+
import { useCallback as useCallback5, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef3, useState as useState6 } from "react";
|
|
817
824
|
|
|
818
825
|
// src/ui/SequenceCanvas.tsx
|
|
819
826
|
import { useMemo } from "react";
|
|
@@ -1039,8 +1046,17 @@ function SequenceCanvas(props) {
|
|
|
1039
1046
|
fontSize: 13,
|
|
1040
1047
|
fontWeight: 700,
|
|
1041
1048
|
fill: t.actorText,
|
|
1049
|
+
role: "button",
|
|
1050
|
+
tabIndex: 0,
|
|
1051
|
+
"aria-label": `Actor ${name} \u2014 press Enter or F2 to rename`,
|
|
1042
1052
|
style: STYLE_SEQ_ACTOR_TEXT,
|
|
1043
1053
|
onDoubleClick: () => setEditingId(name),
|
|
1054
|
+
onKeyDown: (e) => {
|
|
1055
|
+
if (e.key === "Enter" || e.key === "F2") {
|
|
1056
|
+
e.preventDefault();
|
|
1057
|
+
setEditingId(name);
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1044
1060
|
children: name
|
|
1045
1061
|
}
|
|
1046
1062
|
),
|
|
@@ -1051,9 +1067,21 @@ function SequenceCanvas(props) {
|
|
|
1051
1067
|
cy: HEADER_PAD + 14,
|
|
1052
1068
|
r: 9,
|
|
1053
1069
|
fill: "transparent",
|
|
1070
|
+
role: "button",
|
|
1071
|
+
tabIndex: 0,
|
|
1072
|
+
"aria-label": `Remove actor ${name}`,
|
|
1054
1073
|
style: STYLE_SEQ_REMOVE_BTN,
|
|
1055
1074
|
onClick: () => removeActor(name),
|
|
1056
|
-
|
|
1075
|
+
onKeyDown: (e) => {
|
|
1076
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1077
|
+
e.preventDefault();
|
|
1078
|
+
removeActor(name);
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
children: /* @__PURE__ */ jsxs4("title", { children: [
|
|
1082
|
+
"Remove actor ",
|
|
1083
|
+
name
|
|
1084
|
+
] })
|
|
1057
1085
|
}
|
|
1058
1086
|
),
|
|
1059
1087
|
/* @__PURE__ */ jsx4(
|
|
@@ -1520,7 +1548,7 @@ async function toPNG(model) {
|
|
|
1520
1548
|
}
|
|
1521
1549
|
|
|
1522
1550
|
// src/ui/hooks/useExporters.ts
|
|
1523
|
-
function useExporters(model, onExport, filename = "diagram") {
|
|
1551
|
+
function useExporters(model, onExport, filename = "diagram", onSuccess) {
|
|
1524
1552
|
return useCallback2(async (format) => {
|
|
1525
1553
|
let content;
|
|
1526
1554
|
switch (format) {
|
|
@@ -1544,6 +1572,7 @@ function useExporters(model, onExport, filename = "diagram") {
|
|
|
1544
1572
|
}
|
|
1545
1573
|
if (onExport) {
|
|
1546
1574
|
onExport(format, content);
|
|
1575
|
+
onSuccess?.(`Exported as ${format.toUpperCase()}`);
|
|
1547
1576
|
return;
|
|
1548
1577
|
}
|
|
1549
1578
|
const url = content instanceof Blob ? URL.createObjectURL(content) : URL.createObjectURL(new Blob([content], { type: "text/plain" }));
|
|
@@ -1552,7 +1581,8 @@ function useExporters(model, onExport, filename = "diagram") {
|
|
|
1552
1581
|
a.download = `${filename}.${format === "plantuml" ? "puml" : format}`;
|
|
1553
1582
|
a.click();
|
|
1554
1583
|
URL.revokeObjectURL(url);
|
|
1555
|
-
|
|
1584
|
+
onSuccess?.(`Downloaded ${a.download}`);
|
|
1585
|
+
}, [model, onExport, filename, onSuccess]);
|
|
1556
1586
|
}
|
|
1557
1587
|
|
|
1558
1588
|
// src/ui/hooks/useImporter.ts
|
|
@@ -1850,19 +1880,91 @@ function fromJSON(json) {
|
|
|
1850
1880
|
|
|
1851
1881
|
// src/ui/hooks/useImporter.ts
|
|
1852
1882
|
function useImporter(applyAndPush, options = {}) {
|
|
1853
|
-
const { expectedType, transform } = options;
|
|
1883
|
+
const { expectedType, transform, onSuccess, onError } = options;
|
|
1884
|
+
const reportError = onError ?? ((msg) => alert(msg));
|
|
1854
1885
|
return useCallback3((text) => {
|
|
1855
1886
|
try {
|
|
1856
1887
|
const parsed = text.trim().startsWith("{") ? fromJSON(text).toJSON() : fromMermaid(text).toJSON();
|
|
1857
1888
|
if (expectedType && parsed.type !== expectedType) {
|
|
1858
|
-
|
|
1889
|
+
reportError(`Imported diagram is not a ${expectedType} diagram.`);
|
|
1859
1890
|
return;
|
|
1860
1891
|
}
|
|
1861
1892
|
applyAndPush(transform ? transform(parsed) : parsed);
|
|
1893
|
+
onSuccess?.("Diagram imported successfully");
|
|
1862
1894
|
} catch (err) {
|
|
1863
|
-
|
|
1895
|
+
reportError(`Import failed: ${err.message}`);
|
|
1864
1896
|
}
|
|
1865
|
-
}, [applyAndPush, expectedType, transform]);
|
|
1897
|
+
}, [applyAndPush, expectedType, transform, onSuccess, onError]);
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// src/ui/hooks/useToast.ts
|
|
1901
|
+
import { useCallback as useCallback4, useState as useState5 } from "react";
|
|
1902
|
+
var _toastSeq = 0;
|
|
1903
|
+
function useToast() {
|
|
1904
|
+
const [toasts, setToasts] = useState5([]);
|
|
1905
|
+
const showToast = useCallback4((message, type = "info") => {
|
|
1906
|
+
const id = ++_toastSeq;
|
|
1907
|
+
setToasts((prev) => [...prev, { id, message, type }]);
|
|
1908
|
+
setTimeout(() => {
|
|
1909
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
1910
|
+
}, 3e3);
|
|
1911
|
+
}, []);
|
|
1912
|
+
const dismissToast = useCallback4((id) => {
|
|
1913
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
1914
|
+
}, []);
|
|
1915
|
+
return { toasts, showToast, dismissToast };
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// src/ui/ToastContainer.tsx
|
|
1919
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1920
|
+
var TOAST_COLORS = {
|
|
1921
|
+
success: { bg: "#065f46", border: "#10b981", text: "#ecfdf5" },
|
|
1922
|
+
error: { bg: "#7f1d1d", border: "#ef4444", text: "#fef2f2" },
|
|
1923
|
+
info: { bg: "#1e3a5f", border: "#3b82f6", text: "#eff6ff" }
|
|
1924
|
+
};
|
|
1925
|
+
var containerStyle = {
|
|
1926
|
+
position: "absolute",
|
|
1927
|
+
top: 8,
|
|
1928
|
+
right: 8,
|
|
1929
|
+
display: "flex",
|
|
1930
|
+
flexDirection: "column",
|
|
1931
|
+
gap: 6,
|
|
1932
|
+
zIndex: 9999,
|
|
1933
|
+
pointerEvents: "none"
|
|
1934
|
+
};
|
|
1935
|
+
function ToastContainer({ toasts, onDismiss }) {
|
|
1936
|
+
if (toasts.length === 0) return null;
|
|
1937
|
+
return /* @__PURE__ */ jsx5("div", { style: containerStyle, children: toasts.map((t) => {
|
|
1938
|
+
const c = TOAST_COLORS[t.type];
|
|
1939
|
+
return /* @__PURE__ */ jsxs5(
|
|
1940
|
+
"div",
|
|
1941
|
+
{
|
|
1942
|
+
role: "alert",
|
|
1943
|
+
"aria-live": "polite",
|
|
1944
|
+
style: {
|
|
1945
|
+
background: c.bg,
|
|
1946
|
+
border: `1px solid ${c.border}`,
|
|
1947
|
+
color: c.text,
|
|
1948
|
+
padding: "8px 14px",
|
|
1949
|
+
borderRadius: 8,
|
|
1950
|
+
fontSize: 12,
|
|
1951
|
+
fontWeight: 500,
|
|
1952
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
1953
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
|
|
1954
|
+
pointerEvents: "auto",
|
|
1955
|
+
cursor: "pointer",
|
|
1956
|
+
maxWidth: 280
|
|
1957
|
+
},
|
|
1958
|
+
onClick: () => onDismiss(t.id),
|
|
1959
|
+
children: [
|
|
1960
|
+
t.type === "success" && "\u2713 ",
|
|
1961
|
+
t.type === "error" && "\u2717 ",
|
|
1962
|
+
t.message
|
|
1963
|
+
]
|
|
1964
|
+
},
|
|
1965
|
+
t.id
|
|
1966
|
+
);
|
|
1967
|
+
}) });
|
|
1866
1968
|
}
|
|
1867
1969
|
|
|
1868
1970
|
// src/ui/presets.ts
|
|
@@ -1985,7 +2087,7 @@ function useEditorKeyboard(commands, deps) {
|
|
|
1985
2087
|
}
|
|
1986
2088
|
|
|
1987
2089
|
// src/ui/SequenceEditor.tsx
|
|
1988
|
-
import { jsx as
|
|
2090
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1989
2091
|
var INDIGO2 = "#4f46e5";
|
|
1990
2092
|
var INDIGO_SOFT2 = "#eef2ff";
|
|
1991
2093
|
var lightTheme2 = {
|
|
@@ -2052,11 +2154,12 @@ function SequenceEditor({
|
|
|
2052
2154
|
theme = "auto",
|
|
2053
2155
|
themeOverrides
|
|
2054
2156
|
}) {
|
|
2055
|
-
const [model, setModel] =
|
|
2056
|
-
const
|
|
2057
|
-
const [
|
|
2058
|
-
const [
|
|
2059
|
-
const [
|
|
2157
|
+
const [model, setModel] = useState6(() => ensureSequenceModel(initialModel));
|
|
2158
|
+
const { toasts, showToast, dismissToast } = useToast();
|
|
2159
|
+
const [selected, setSelected] = useState6(null);
|
|
2160
|
+
const [drag, setDrag] = useState6(null);
|
|
2161
|
+
const [editingId, setEditingId] = useState6(null);
|
|
2162
|
+
const [editLabel, setEditLabel] = useState6("");
|
|
2060
2163
|
const historyRef = useRef3([ensureSequenceModel(initialModel)]);
|
|
2061
2164
|
const historyIdxRef = useRef3(0);
|
|
2062
2165
|
const svgRef = useRef3(null);
|
|
@@ -2075,26 +2178,26 @@ function SequenceEditor({
|
|
|
2075
2178
|
return SIDE_PAD2 + idx * colW + colW / 2;
|
|
2076
2179
|
};
|
|
2077
2180
|
const msgY = (idx) => HEADER_PAD2 + HEADER_H2 + 40 + idx * ROW_H2;
|
|
2078
|
-
const pushHistory =
|
|
2181
|
+
const pushHistory = useCallback5((m) => {
|
|
2079
2182
|
const stack = historyRef.current.slice(0, historyIdxRef.current + 1);
|
|
2080
2183
|
stack.push(m);
|
|
2081
2184
|
if (stack.length > 80) stack.shift();
|
|
2082
2185
|
historyRef.current = stack;
|
|
2083
2186
|
historyIdxRef.current = stack.length - 1;
|
|
2084
2187
|
}, []);
|
|
2085
|
-
const applyAndPush =
|
|
2188
|
+
const applyAndPush = useCallback5((m) => {
|
|
2086
2189
|
setModel(m);
|
|
2087
2190
|
onChange?.(m);
|
|
2088
2191
|
pushHistory(m);
|
|
2089
2192
|
}, [onChange, pushHistory]);
|
|
2090
|
-
const undo =
|
|
2193
|
+
const undo = useCallback5(() => {
|
|
2091
2194
|
if (historyIdxRef.current <= 0) return;
|
|
2092
2195
|
historyIdxRef.current--;
|
|
2093
2196
|
const m = historyRef.current[historyIdxRef.current];
|
|
2094
2197
|
setModel(m);
|
|
2095
2198
|
onChange?.(m);
|
|
2096
2199
|
}, [onChange]);
|
|
2097
|
-
const redo =
|
|
2200
|
+
const redo = useCallback5(() => {
|
|
2098
2201
|
if (historyIdxRef.current >= historyRef.current.length - 1) return;
|
|
2099
2202
|
historyIdxRef.current++;
|
|
2100
2203
|
const m = historyRef.current[historyIdxRef.current];
|
|
@@ -2152,7 +2255,7 @@ function SequenceEditor({
|
|
|
2152
2255
|
applyAndPush({ ...model, messages: messages.filter((m) => m.id !== id) });
|
|
2153
2256
|
if (selected === id) setSelected(null);
|
|
2154
2257
|
};
|
|
2155
|
-
const reorderMessage =
|
|
2258
|
+
const reorderMessage = useCallback5((id, toIdx) => {
|
|
2156
2259
|
const fromIdx = messages.findIndex((m) => m.id === id);
|
|
2157
2260
|
if (fromIdx < 0 || toIdx === fromIdx) return;
|
|
2158
2261
|
const next = messages.slice();
|
|
@@ -2180,10 +2283,12 @@ function SequenceEditor({
|
|
|
2180
2283
|
} }
|
|
2181
2284
|
];
|
|
2182
2285
|
useEditorKeyboard(keyCommands, [undo, redo, selected]);
|
|
2183
|
-
const handleExport = useExporters(model, onExport, "sequence");
|
|
2286
|
+
const handleExport = useExporters(model, onExport, "sequence", (msg) => showToast(msg, "success"));
|
|
2184
2287
|
const handleImport = useImporter(applyAndPush, {
|
|
2185
2288
|
expectedType: "sequence",
|
|
2186
|
-
transform: ensureSequenceModel
|
|
2289
|
+
transform: ensureSequenceModel,
|
|
2290
|
+
onSuccess: (msg) => showToast(msg, "success"),
|
|
2291
|
+
onError: (msg) => showToast(msg, "error")
|
|
2187
2292
|
});
|
|
2188
2293
|
const onRowMouseDown = (e, id) => {
|
|
2189
2294
|
const tag = e.target.tagName;
|
|
@@ -2223,16 +2328,30 @@ function SequenceEditor({
|
|
|
2223
2328
|
};
|
|
2224
2329
|
}, [drag, messages.length, reorderMessage]);
|
|
2225
2330
|
const selectedMsg = selected ? messages.find((m) => m.id === selected) : null;
|
|
2226
|
-
return /* @__PURE__ */
|
|
2331
|
+
return /* @__PURE__ */ jsxs6("div", { className: "fsd-seq-editor", style: {
|
|
2227
2332
|
display: "flex",
|
|
2228
2333
|
flexDirection: "column",
|
|
2229
2334
|
height,
|
|
2230
2335
|
width: "100%",
|
|
2231
2336
|
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
2232
|
-
background: t.ctrlsBg
|
|
2337
|
+
background: t.ctrlsBg,
|
|
2338
|
+
position: "relative"
|
|
2233
2339
|
}, children: [
|
|
2234
|
-
/* @__PURE__ */
|
|
2235
|
-
/* @__PURE__ */
|
|
2340
|
+
/* @__PURE__ */ jsx6(ToastContainer, { toasts, onDismiss: dismissToast }),
|
|
2341
|
+
/* @__PURE__ */ jsx6("style", { children: `
|
|
2342
|
+
.fsd-seq-editor [role="button"]:focus-visible {
|
|
2343
|
+
outline: 2px solid ${t.actorText};
|
|
2344
|
+
outline-offset: 2px;
|
|
2345
|
+
}
|
|
2346
|
+
.fsd-seq-editor button:focus-visible,
|
|
2347
|
+
.fsd-seq-editor input:focus-visible {
|
|
2348
|
+
outline: 2px solid ${t.actorText};
|
|
2349
|
+
outline-offset: 2px;
|
|
2350
|
+
border-radius: 4px;
|
|
2351
|
+
}
|
|
2352
|
+
` }),
|
|
2353
|
+
/* @__PURE__ */ jsx6(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
|
|
2354
|
+
/* @__PURE__ */ jsxs6("div", { style: {
|
|
2236
2355
|
display: "flex",
|
|
2237
2356
|
gap: 8,
|
|
2238
2357
|
padding: "7px 14px",
|
|
@@ -2241,12 +2360,12 @@ function SequenceEditor({
|
|
|
2241
2360
|
alignItems: "center",
|
|
2242
2361
|
flexWrap: "wrap"
|
|
2243
2362
|
}, children: [
|
|
2244
|
-
/* @__PURE__ */
|
|
2245
|
-
/* @__PURE__ */
|
|
2246
|
-
/* @__PURE__ */
|
|
2247
|
-
/* @__PURE__ */
|
|
2248
|
-
/* @__PURE__ */
|
|
2249
|
-
/* @__PURE__ */
|
|
2363
|
+
/* @__PURE__ */ jsx6("button", { onClick: addActor, style: primaryBtn(), children: "+ Actor" }),
|
|
2364
|
+
/* @__PURE__ */ jsx6("button", { onClick: addMessage, style: primaryBtn(), children: "+ Message" }),
|
|
2365
|
+
/* @__PURE__ */ jsx6("div", { style: { width: 1, height: 18, background: t.ctrlsBorder, margin: "0 4px" } }),
|
|
2366
|
+
/* @__PURE__ */ jsx6("button", { onClick: undo, style: ghostBtn2(t), title: "Undo (Ctrl+Z)", children: "\u21B6" }),
|
|
2367
|
+
/* @__PURE__ */ jsx6("button", { onClick: redo, style: ghostBtn2(t), title: "Redo (Ctrl+Y)", children: "\u21B7" }),
|
|
2368
|
+
/* @__PURE__ */ jsxs6("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
2250
2369
|
actors.length,
|
|
2251
2370
|
" actor",
|
|
2252
2371
|
actors.length === 1 ? "" : "s",
|
|
@@ -2257,8 +2376,8 @@ function SequenceEditor({
|
|
|
2257
2376
|
" \xB7 drag a row to reorder"
|
|
2258
2377
|
] })
|
|
2259
2378
|
] }),
|
|
2260
|
-
/* @__PURE__ */
|
|
2261
|
-
/* @__PURE__ */
|
|
2379
|
+
/* @__PURE__ */ jsxs6("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
|
|
2380
|
+
/* @__PURE__ */ jsx6("div", { style: { flex: 1, overflow: "auto", background: t.canvas, position: "relative" }, children: /* @__PURE__ */ jsx6(
|
|
2262
2381
|
SequenceCanvas,
|
|
2263
2382
|
{
|
|
2264
2383
|
model,
|
|
@@ -2281,17 +2400,18 @@ function SequenceEditor({
|
|
|
2281
2400
|
svgRef
|
|
2282
2401
|
}
|
|
2283
2402
|
) }),
|
|
2284
|
-
selectedMsg && /* @__PURE__ */
|
|
2403
|
+
selectedMsg && /* @__PURE__ */ jsxs6("div", { style: {
|
|
2285
2404
|
width: 280,
|
|
2405
|
+
maxWidth: "40vw",
|
|
2286
2406
|
flexShrink: 0,
|
|
2287
2407
|
background: t.panelBg,
|
|
2288
2408
|
borderLeft: `1px solid ${t.panelBorder}`,
|
|
2289
2409
|
padding: "14px 16px",
|
|
2290
2410
|
overflowY: "auto"
|
|
2291
2411
|
}, children: [
|
|
2292
|
-
/* @__PURE__ */
|
|
2293
|
-
/* @__PURE__ */
|
|
2294
|
-
/* @__PURE__ */
|
|
2412
|
+
/* @__PURE__ */ jsx6("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.7, marginBottom: 10 }, children: "Message" }),
|
|
2413
|
+
/* @__PURE__ */ jsx6(Label, { t, children: "Label" }),
|
|
2414
|
+
/* @__PURE__ */ jsx6(
|
|
2295
2415
|
"input",
|
|
2296
2416
|
{
|
|
2297
2417
|
value: editLabel || selectedMsg.label,
|
|
@@ -2307,12 +2427,12 @@ function SequenceEditor({
|
|
|
2307
2427
|
style: input(t)
|
|
2308
2428
|
}
|
|
2309
2429
|
),
|
|
2310
|
-
/* @__PURE__ */
|
|
2311
|
-
/* @__PURE__ */
|
|
2312
|
-
/* @__PURE__ */
|
|
2313
|
-
/* @__PURE__ */
|
|
2314
|
-
/* @__PURE__ */
|
|
2315
|
-
/* @__PURE__ */
|
|
2430
|
+
/* @__PURE__ */ jsx6(Label, { t, children: "From" }),
|
|
2431
|
+
/* @__PURE__ */ jsx6("select", { value: selectedMsg.from, onChange: (e) => updateMessage(selectedMsg.id, { from: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ jsx6("option", { value: a, children: a }, a)) }),
|
|
2432
|
+
/* @__PURE__ */ jsx6(Label, { t, children: "To" }),
|
|
2433
|
+
/* @__PURE__ */ jsx6("select", { value: selectedMsg.to, onChange: (e) => updateMessage(selectedMsg.id, { to: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ jsx6("option", { value: a, children: a }, a)) }),
|
|
2434
|
+
/* @__PURE__ */ jsx6(Label, { t, children: "Style" }),
|
|
2435
|
+
/* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: 6 }, children: ["solid", "dashed"].map((s2) => /* @__PURE__ */ jsx6(
|
|
2316
2436
|
"button",
|
|
2317
2437
|
{
|
|
2318
2438
|
onClick: () => updateMessage(selectedMsg.id, { style: s2 }),
|
|
@@ -2332,8 +2452,8 @@ function SequenceEditor({
|
|
|
2332
2452
|
},
|
|
2333
2453
|
s2
|
|
2334
2454
|
)) }),
|
|
2335
|
-
/* @__PURE__ */
|
|
2336
|
-
/* @__PURE__ */
|
|
2455
|
+
/* @__PURE__ */ jsx6("div", { style: { height: 14 } }),
|
|
2456
|
+
/* @__PURE__ */ jsx6(
|
|
2337
2457
|
"button",
|
|
2338
2458
|
{
|
|
2339
2459
|
onClick: () => removeMessage(selectedMsg.id),
|
|
@@ -2343,7 +2463,7 @@ function SequenceEditor({
|
|
|
2343
2463
|
)
|
|
2344
2464
|
] })
|
|
2345
2465
|
] }),
|
|
2346
|
-
/* @__PURE__ */
|
|
2466
|
+
/* @__PURE__ */ jsxs6("div", { style: {
|
|
2347
2467
|
padding: "4px 14px",
|
|
2348
2468
|
fontSize: 11,
|
|
2349
2469
|
color: t.textMuted,
|
|
@@ -2352,15 +2472,15 @@ function SequenceEditor({
|
|
|
2352
2472
|
display: "flex",
|
|
2353
2473
|
gap: 16
|
|
2354
2474
|
}, children: [
|
|
2355
|
-
/* @__PURE__ */
|
|
2475
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
2356
2476
|
actors.length,
|
|
2357
2477
|
" actors"
|
|
2358
2478
|
] }),
|
|
2359
|
-
/* @__PURE__ */
|
|
2479
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
2360
2480
|
messages.length,
|
|
2361
2481
|
" messages"
|
|
2362
2482
|
] }),
|
|
2363
|
-
/* @__PURE__ */
|
|
2483
|
+
/* @__PURE__ */ jsx6("span", { style: { marginLeft: "auto" }, children: "double-click actor to rename \xB7 drag a row to reorder" })
|
|
2364
2484
|
] })
|
|
2365
2485
|
] });
|
|
2366
2486
|
}
|
|
@@ -2406,12 +2526,12 @@ function input(t) {
|
|
|
2406
2526
|
};
|
|
2407
2527
|
}
|
|
2408
2528
|
function Label({ t, children }) {
|
|
2409
|
-
return /* @__PURE__ */
|
|
2529
|
+
return /* @__PURE__ */ jsx6("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.6, marginBottom: 4 }, children });
|
|
2410
2530
|
}
|
|
2411
2531
|
|
|
2412
2532
|
// src/ui/NodeNavigator.tsx
|
|
2413
|
-
import { useState as
|
|
2414
|
-
import { jsx as
|
|
2533
|
+
import { useState as useState7 } from "react";
|
|
2534
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2415
2535
|
function NodeNavigator({
|
|
2416
2536
|
model,
|
|
2417
2537
|
selected,
|
|
@@ -2423,7 +2543,7 @@ function NodeNavigator({
|
|
|
2423
2543
|
onToggle,
|
|
2424
2544
|
onSelect
|
|
2425
2545
|
}) {
|
|
2426
|
-
const [search, setSearch] =
|
|
2546
|
+
const [search, setSearch] = useState7("");
|
|
2427
2547
|
const shapeIcon = (node) => {
|
|
2428
2548
|
if (variant === "question") return "?";
|
|
2429
2549
|
if (variant === "journey") return "\u2197";
|
|
@@ -2444,7 +2564,7 @@ function NodeNavigator({
|
|
|
2444
2564
|
const inEdges = (id) => model.edges.filter((e) => e.to === id).length;
|
|
2445
2565
|
const outEdges = (id) => model.edges.filter((e) => e.from === id).length;
|
|
2446
2566
|
if (!open) {
|
|
2447
|
-
return /* @__PURE__ */
|
|
2567
|
+
return /* @__PURE__ */ jsxs7("div", { style: {
|
|
2448
2568
|
width: 36,
|
|
2449
2569
|
flexShrink: 0,
|
|
2450
2570
|
background: t.panelBg,
|
|
@@ -2455,19 +2575,21 @@ function NodeNavigator({
|
|
|
2455
2575
|
paddingTop: 8,
|
|
2456
2576
|
gap: 6
|
|
2457
2577
|
}, children: [
|
|
2458
|
-
/* @__PURE__ */
|
|
2578
|
+
/* @__PURE__ */ jsx7(
|
|
2459
2579
|
"button",
|
|
2460
2580
|
{
|
|
2461
2581
|
onClick: onToggle,
|
|
2462
2582
|
title: "Open node list",
|
|
2583
|
+
"aria-expanded": false,
|
|
2584
|
+
"aria-label": "Open node list",
|
|
2463
2585
|
style: { background: "none", border: "none", cursor: "pointer", color: t.textMuted, padding: 6, borderRadius: 6, fontSize: 14, lineHeight: 1 },
|
|
2464
2586
|
children: "\u2630"
|
|
2465
2587
|
}
|
|
2466
2588
|
),
|
|
2467
|
-
/* @__PURE__ */
|
|
2589
|
+
/* @__PURE__ */ jsx7("div", { style: { fontSize: 10, color: t.textMuted, fontWeight: 700, writingMode: "vertical-rl", transform: "rotate(180deg)", letterSpacing: 0.5 }, children: model.nodes.length })
|
|
2468
2590
|
] });
|
|
2469
2591
|
}
|
|
2470
|
-
return /* @__PURE__ */
|
|
2592
|
+
return /* @__PURE__ */ jsxs7("div", { style: {
|
|
2471
2593
|
width: 216,
|
|
2472
2594
|
flexShrink: 0,
|
|
2473
2595
|
background: t.panelBg,
|
|
@@ -2476,7 +2598,7 @@ function NodeNavigator({
|
|
|
2476
2598
|
flexDirection: "column",
|
|
2477
2599
|
overflow: "hidden"
|
|
2478
2600
|
}, children: [
|
|
2479
|
-
/* @__PURE__ */
|
|
2601
|
+
/* @__PURE__ */ jsxs7("div", { style: {
|
|
2480
2602
|
display: "flex",
|
|
2481
2603
|
alignItems: "center",
|
|
2482
2604
|
justifyContent: "space-between",
|
|
@@ -2484,9 +2606,9 @@ function NodeNavigator({
|
|
|
2484
2606
|
borderBottom: `1px solid ${t.panelBorder}`,
|
|
2485
2607
|
flexShrink: 0
|
|
2486
2608
|
}, children: [
|
|
2487
|
-
/* @__PURE__ */
|
|
2488
|
-
/* @__PURE__ */
|
|
2489
|
-
/* @__PURE__ */
|
|
2609
|
+
/* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
2610
|
+
/* @__PURE__ */ jsx7("span", { style: { fontSize: 11, fontWeight: 700, color: t.textSecondary, textTransform: "uppercase", letterSpacing: 0.7 }, children: variant === "question" ? "Questions" : variant === "journey" ? "Steps" : "Nodes" }),
|
|
2611
|
+
/* @__PURE__ */ jsx7("span", { style: {
|
|
2490
2612
|
fontSize: 10,
|
|
2491
2613
|
fontWeight: 700,
|
|
2492
2614
|
color: t.textMuted,
|
|
@@ -2495,19 +2617,21 @@ function NodeNavigator({
|
|
|
2495
2617
|
borderRadius: 99
|
|
2496
2618
|
}, children: model.nodes.length })
|
|
2497
2619
|
] }),
|
|
2498
|
-
/* @__PURE__ */
|
|
2620
|
+
/* @__PURE__ */ jsx7(
|
|
2499
2621
|
"button",
|
|
2500
2622
|
{
|
|
2501
2623
|
onClick: onToggle,
|
|
2502
2624
|
style: { background: "none", border: "none", cursor: "pointer", color: t.textMuted, padding: "2px 4px", borderRadius: 4, fontSize: 13, lineHeight: 1 },
|
|
2503
2625
|
title: "Collapse",
|
|
2626
|
+
"aria-expanded": true,
|
|
2627
|
+
"aria-label": "Collapse node list",
|
|
2504
2628
|
children: "\u2039"
|
|
2505
2629
|
}
|
|
2506
2630
|
)
|
|
2507
2631
|
] }),
|
|
2508
|
-
/* @__PURE__ */
|
|
2509
|
-
/* @__PURE__ */
|
|
2510
|
-
/* @__PURE__ */
|
|
2632
|
+
/* @__PURE__ */ jsx7("div", { style: { padding: "8px 10px", borderBottom: `1px solid ${t.sectionBorder}`, flexShrink: 0 }, children: /* @__PURE__ */ jsxs7("div", { style: { position: "relative" }, children: [
|
|
2633
|
+
/* @__PURE__ */ jsx7("span", { style: { position: "absolute", left: 8, top: "50%", transform: "translateY(-50%)", fontSize: 11, color: t.textMuted, pointerEvents: "none" }, children: "\u2315" }),
|
|
2634
|
+
/* @__PURE__ */ jsx7(
|
|
2511
2635
|
"input",
|
|
2512
2636
|
{
|
|
2513
2637
|
value: search,
|
|
@@ -2528,12 +2652,12 @@ function NodeNavigator({
|
|
|
2528
2652
|
}
|
|
2529
2653
|
)
|
|
2530
2654
|
] }) }),
|
|
2531
|
-
/* @__PURE__ */
|
|
2532
|
-
filtered.length === 0 && /* @__PURE__ */
|
|
2655
|
+
/* @__PURE__ */ jsxs7("div", { style: { flex: 1, overflowY: "auto", padding: "6px 8px", display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
2656
|
+
filtered.length === 0 && /* @__PURE__ */ jsx7("div", { style: { textAlign: "center", padding: "20px 0", fontSize: 12, color: t.textMuted, fontStyle: "italic" }, children: model.nodes.length === 0 ? "No nodes yet" : "No matches" }),
|
|
2533
2657
|
filtered.map((node, idx) => {
|
|
2534
2658
|
const isSelected = selected === node.id;
|
|
2535
2659
|
const answers = node.metadata?.answers ?? [];
|
|
2536
|
-
return /* @__PURE__ */
|
|
2660
|
+
return /* @__PURE__ */ jsxs7(
|
|
2537
2661
|
"button",
|
|
2538
2662
|
{
|
|
2539
2663
|
onClick: () => onSelect(node.id),
|
|
@@ -2558,7 +2682,7 @@ function NodeNavigator({
|
|
|
2558
2682
|
if (!isSelected) e.currentTarget.style.background = "transparent";
|
|
2559
2683
|
},
|
|
2560
2684
|
children: [
|
|
2561
|
-
/* @__PURE__ */
|
|
2685
|
+
/* @__PURE__ */ jsx7("div", { style: {
|
|
2562
2686
|
width: 22,
|
|
2563
2687
|
height: 22,
|
|
2564
2688
|
borderRadius: 6,
|
|
@@ -2571,8 +2695,8 @@ function NodeNavigator({
|
|
|
2571
2695
|
fontSize: variant === "journey" ? 9 : 11,
|
|
2572
2696
|
fontWeight: 700
|
|
2573
2697
|
}, children: variant === "journey" ? idx + 1 : shapeIcon(node) }),
|
|
2574
|
-
/* @__PURE__ */
|
|
2575
|
-
/* @__PURE__ */
|
|
2698
|
+
/* @__PURE__ */ jsxs7("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
2699
|
+
/* @__PURE__ */ jsx7("div", { style: {
|
|
2576
2700
|
fontSize: 12,
|
|
2577
2701
|
fontWeight: isSelected ? 600 : 400,
|
|
2578
2702
|
color: isSelected ? acc.color : t.textPrimary,
|
|
@@ -2581,9 +2705,9 @@ function NodeNavigator({
|
|
|
2581
2705
|
whiteSpace: "nowrap",
|
|
2582
2706
|
lineHeight: 1.3
|
|
2583
2707
|
}, children: node.label }),
|
|
2584
|
-
/* @__PURE__ */
|
|
2708
|
+
/* @__PURE__ */ jsx7("div", { style: { fontSize: 10, color: t.textMuted, lineHeight: 1.2, marginTop: 1 }, children: variant === "question" ? `${answers.length} answer${answers.length !== 1 ? "s" : ""}` : `${inEdges(node.id)}\u2193 ${outEdges(node.id)}\u2192` })
|
|
2585
2709
|
] }),
|
|
2586
|
-
isSelected && /* @__PURE__ */
|
|
2710
|
+
isSelected && /* @__PURE__ */ jsx7("span", { style: { fontSize: 10, color: acc.color, flexShrink: 0 }, children: "\u25C9" })
|
|
2587
2711
|
]
|
|
2588
2712
|
},
|
|
2589
2713
|
node.id
|
|
@@ -2594,7 +2718,7 @@ function NodeNavigator({
|
|
|
2594
2718
|
}
|
|
2595
2719
|
|
|
2596
2720
|
// src/ui/render.tsx
|
|
2597
|
-
import { useState as
|
|
2721
|
+
import { useState as useState8 } from "react";
|
|
2598
2722
|
|
|
2599
2723
|
// src/ui/layout.ts
|
|
2600
2724
|
var NODE_H2 = 48;
|
|
@@ -2660,7 +2784,7 @@ function bezierPathVia(x1, y1, wx, wy, x2, y2) {
|
|
|
2660
2784
|
}
|
|
2661
2785
|
|
|
2662
2786
|
// src/ui/render.tsx
|
|
2663
|
-
import { Fragment as Fragment2, jsx as
|
|
2787
|
+
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2664
2788
|
var STYLE_LABEL = { pointerEvents: "none", userSelect: "none" };
|
|
2665
2789
|
var STYLE_BLUR = { filter: "blur(4px)" };
|
|
2666
2790
|
var STYLE_EDGE_HIT = { cursor: "pointer" };
|
|
@@ -2674,11 +2798,11 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2674
2798
|
const stroke = selected ? acc.color : t.nodeStroke;
|
|
2675
2799
|
const fill = selected ? t.nodeSelectedFill : t.nodeFill;
|
|
2676
2800
|
const sw = selected ? 1.75 : 1.25;
|
|
2677
|
-
const glow = selected && /* @__PURE__ */
|
|
2678
|
-
/* @__PURE__ */
|
|
2679
|
-
/* @__PURE__ */
|
|
2680
|
-
] }) : node.shape === "diamond" ? /* @__PURE__ */
|
|
2681
|
-
/* @__PURE__ */
|
|
2801
|
+
const glow = selected && /* @__PURE__ */ jsx8(Fragment2, { children: node.shape === "circle" ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2802
|
+
/* @__PURE__ */ jsx8("circle", { cx, cy, r: NODE_H2 / 2 + 3, fill: "none", stroke: acc.color, strokeWidth: 6, opacity: 0.18, style: STYLE_BLUR }),
|
|
2803
|
+
/* @__PURE__ */ jsx8("circle", { cx, cy, r: NODE_H2 / 2 + 1.5, fill: "none", stroke: acc.color, strokeWidth: 1, opacity: 0.55 })
|
|
2804
|
+
] }) : node.shape === "diamond" ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2805
|
+
/* @__PURE__ */ jsx8(
|
|
2682
2806
|
"polygon",
|
|
2683
2807
|
{
|
|
2684
2808
|
points: `${cx},${-5} ${w + 5},${cy} ${cx},${NODE_H2 + 5} ${-5},${cy}`,
|
|
@@ -2689,7 +2813,7 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2689
2813
|
style: STYLE_BLUR
|
|
2690
2814
|
}
|
|
2691
2815
|
),
|
|
2692
|
-
/* @__PURE__ */
|
|
2816
|
+
/* @__PURE__ */ jsx8(
|
|
2693
2817
|
"polygon",
|
|
2694
2818
|
{
|
|
2695
2819
|
points: `${cx},${-2} ${w + 2},${cy} ${cx},${NODE_H2 + 2} ${-2},${cy}`,
|
|
@@ -2699,8 +2823,8 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2699
2823
|
opacity: 0.55
|
|
2700
2824
|
}
|
|
2701
2825
|
)
|
|
2702
|
-
] }) : /* @__PURE__ */
|
|
2703
|
-
/* @__PURE__ */
|
|
2826
|
+
] }) : /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2827
|
+
/* @__PURE__ */ jsx8(
|
|
2704
2828
|
"rect",
|
|
2705
2829
|
{
|
|
2706
2830
|
x: -4,
|
|
@@ -2715,7 +2839,7 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2715
2839
|
style: STYLE_BLUR
|
|
2716
2840
|
}
|
|
2717
2841
|
),
|
|
2718
|
-
/* @__PURE__ */
|
|
2842
|
+
/* @__PURE__ */ jsx8(
|
|
2719
2843
|
"rect",
|
|
2720
2844
|
{
|
|
2721
2845
|
x: -1.5,
|
|
@@ -2731,35 +2855,35 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2731
2855
|
)
|
|
2732
2856
|
] }) });
|
|
2733
2857
|
const badgeColor = isDark ? ACCENT.emeraldDark : ACCENT.emerald;
|
|
2734
|
-
const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */
|
|
2735
|
-
/* @__PURE__ */
|
|
2736
|
-
/* @__PURE__ */
|
|
2858
|
+
const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2859
|
+
/* @__PURE__ */ jsx8("circle", { cx: 14, cy: 14, r: 10, fill: badgeColor }),
|
|
2860
|
+
/* @__PURE__ */ jsx8("text", { x: 14, y: 18, textAnchor: "middle", fontSize: 9, fill: "white", fontWeight: "700", style: STYLE_LABEL, children: stepNumber })
|
|
2737
2861
|
] });
|
|
2738
2862
|
switch (node.shape) {
|
|
2739
2863
|
case "diamond": {
|
|
2740
2864
|
const pts = `${cx},0 ${w},${cy} ${cx},${NODE_H2} 0,${cy}`;
|
|
2741
|
-
return /* @__PURE__ */
|
|
2865
|
+
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2742
2866
|
glow,
|
|
2743
|
-
/* @__PURE__ */
|
|
2867
|
+
/* @__PURE__ */ jsx8("polygon", { points: pts, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2744
2868
|
badge
|
|
2745
2869
|
] });
|
|
2746
2870
|
}
|
|
2747
2871
|
case "circle":
|
|
2748
|
-
return /* @__PURE__ */
|
|
2872
|
+
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2749
2873
|
glow,
|
|
2750
|
-
/* @__PURE__ */
|
|
2874
|
+
/* @__PURE__ */ jsx8("circle", { cx, cy, r: NODE_H2 / 2 - 1, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2751
2875
|
badge
|
|
2752
2876
|
] });
|
|
2753
2877
|
case "parallelogram":
|
|
2754
|
-
return /* @__PURE__ */
|
|
2878
|
+
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2755
2879
|
glow,
|
|
2756
|
-
/* @__PURE__ */
|
|
2880
|
+
/* @__PURE__ */ jsx8("polygon", { points: `14,0 ${w},0 ${w - 14},${NODE_H2} 0,${NODE_H2}`, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2757
2881
|
badge
|
|
2758
2882
|
] });
|
|
2759
2883
|
default:
|
|
2760
|
-
return /* @__PURE__ */
|
|
2884
|
+
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2761
2885
|
glow,
|
|
2762
|
-
/* @__PURE__ */
|
|
2886
|
+
/* @__PURE__ */ jsx8("rect", { width: w, height: NODE_H2, rx: 14, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2763
2887
|
badge
|
|
2764
2888
|
] });
|
|
2765
2889
|
}
|
|
@@ -2780,8 +2904,8 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2780
2904
|
const textSub = isDark ? "#64748b" : "#94a3b8";
|
|
2781
2905
|
const textAns = isDark ? "#cbd5e1" : "#374151";
|
|
2782
2906
|
const portRowY = Q_BASE_H2 + Q_ANS_ROW_H2 - 8;
|
|
2783
|
-
const glow = selected && /* @__PURE__ */
|
|
2784
|
-
/* @__PURE__ */
|
|
2907
|
+
const glow = selected && /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2908
|
+
/* @__PURE__ */ jsx8(
|
|
2785
2909
|
"rect",
|
|
2786
2910
|
{
|
|
2787
2911
|
x: -4,
|
|
@@ -2796,7 +2920,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2796
2920
|
style: STYLE_BLUR
|
|
2797
2921
|
}
|
|
2798
2922
|
),
|
|
2799
|
-
/* @__PURE__ */
|
|
2923
|
+
/* @__PURE__ */ jsx8(
|
|
2800
2924
|
"rect",
|
|
2801
2925
|
{
|
|
2802
2926
|
x: -1.5,
|
|
@@ -2811,29 +2935,29 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2811
2935
|
}
|
|
2812
2936
|
)
|
|
2813
2937
|
] });
|
|
2814
|
-
return /* @__PURE__ */
|
|
2938
|
+
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2815
2939
|
glow,
|
|
2816
|
-
/* @__PURE__ */
|
|
2817
|
-
/* @__PURE__ */
|
|
2818
|
-
/* @__PURE__ */
|
|
2819
|
-
/* @__PURE__ */
|
|
2820
|
-
/* @__PURE__ */
|
|
2821
|
-
/* @__PURE__ */
|
|
2822
|
-
/* @__PURE__ */
|
|
2940
|
+
/* @__PURE__ */ jsx8("rect", { width: qW, height: totalH, rx: 14, fill: nodeBg, stroke: nodeBorder, strokeWidth: selected ? 2 : 1.5, filter: "url(#nodeShadow)" }),
|
|
2941
|
+
/* @__PURE__ */ jsx8("clipPath", { id: `qhdr-${node.id}`, children: /* @__PURE__ */ jsx8("rect", { width: qW, height: Q_BASE_H2, rx: 14 }) }),
|
|
2942
|
+
/* @__PURE__ */ jsx8("rect", { width: qW, height: Q_BASE_H2, fill: amberSoft, clipPath: `url(#qhdr-${node.id})` }),
|
|
2943
|
+
/* @__PURE__ */ jsx8("rect", { x: 0, y: 0, width: 4, height: Q_BASE_H2, rx: 2, fill: amber }),
|
|
2944
|
+
/* @__PURE__ */ jsx8("rect", { x: 12, y: 14, width: 28, height: 28, rx: 8, fill: amber }),
|
|
2945
|
+
/* @__PURE__ */ jsx8("text", { x: 26, y: 33, textAnchor: "middle", fontSize: 15, fontWeight: "900", fill: "white", style: STYLE_LABEL, children: "?" }),
|
|
2946
|
+
/* @__PURE__ */ jsxs8(
|
|
2823
2947
|
"text",
|
|
2824
2948
|
{
|
|
2825
2949
|
style: STYLE_LABEL,
|
|
2826
2950
|
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
2827
2951
|
children: [
|
|
2828
|
-
/* @__PURE__ */
|
|
2829
|
-
/* @__PURE__ */
|
|
2952
|
+
/* @__PURE__ */ jsx8("tspan", { x: 50, y: 27, fontSize: 9, fontWeight: 700, fill: textSub, letterSpacing: 0.6, textAnchor: "start", children: "QUESTION" }),
|
|
2953
|
+
/* @__PURE__ */ jsx8("tspan", { x: 50, dy: 15, fontSize: 13, fontWeight: 700, fill: selected ? amber : textMain, textAnchor: "start", children: node.label })
|
|
2830
2954
|
]
|
|
2831
2955
|
}
|
|
2832
2956
|
),
|
|
2833
|
-
/* @__PURE__ */
|
|
2834
|
-
answers.length === 0 && /* @__PURE__ */
|
|
2835
|
-
/* @__PURE__ */
|
|
2836
|
-
/* @__PURE__ */
|
|
2957
|
+
/* @__PURE__ */ jsx8("line", { x1: 0, y1: Q_BASE_H2, x2: qW, y2: Q_BASE_H2, stroke: amberLine, strokeWidth: 1 }),
|
|
2958
|
+
answers.length === 0 && /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2959
|
+
/* @__PURE__ */ jsx8("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" }),
|
|
2960
|
+
/* @__PURE__ */ jsx8("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" })
|
|
2837
2961
|
] }),
|
|
2838
2962
|
answers.map((ans, i) => {
|
|
2839
2963
|
const prevW = answers.slice(0, i).reduce((s2, a) => s2 + answerCardW2(a) + Q_CARD_PAD2, 0);
|
|
@@ -2846,8 +2970,8 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2846
2970
|
const letter = i < 26 ? ANSWER_LETTERS[i] : `${i + 1}`;
|
|
2847
2971
|
const maxChars = Math.max(2, Math.floor((cW - 20) / 7.5));
|
|
2848
2972
|
const displayAns = ans.length > maxChars ? ans.slice(0, maxChars - 1) + "\u2026" : ans;
|
|
2849
|
-
return /* @__PURE__ */
|
|
2850
|
-
/* @__PURE__ */
|
|
2973
|
+
return /* @__PURE__ */ jsxs8("g", { children: [
|
|
2974
|
+
/* @__PURE__ */ jsx8(
|
|
2851
2975
|
"rect",
|
|
2852
2976
|
{
|
|
2853
2977
|
x: cardX,
|
|
@@ -2860,7 +2984,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2860
2984
|
strokeWidth: connected ? 1.5 : 1
|
|
2861
2985
|
}
|
|
2862
2986
|
),
|
|
2863
|
-
/* @__PURE__ */
|
|
2987
|
+
/* @__PURE__ */ jsx8(
|
|
2864
2988
|
"rect",
|
|
2865
2989
|
{
|
|
2866
2990
|
x: cx - 11,
|
|
@@ -2871,7 +2995,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2871
2995
|
fill: connected ? amber : isDark ? "#1e293b" : "#fef3c7"
|
|
2872
2996
|
}
|
|
2873
2997
|
),
|
|
2874
|
-
/* @__PURE__ */
|
|
2998
|
+
/* @__PURE__ */ jsx8(
|
|
2875
2999
|
"text",
|
|
2876
3000
|
{
|
|
2877
3001
|
x: cx,
|
|
@@ -2884,7 +3008,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2884
3008
|
children: letter
|
|
2885
3009
|
}
|
|
2886
3010
|
),
|
|
2887
|
-
/* @__PURE__ */
|
|
3011
|
+
/* @__PURE__ */ jsx8(
|
|
2888
3012
|
"text",
|
|
2889
3013
|
{
|
|
2890
3014
|
x: cx,
|
|
@@ -2898,7 +3022,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2898
3022
|
children: displayAns
|
|
2899
3023
|
}
|
|
2900
3024
|
),
|
|
2901
|
-
/* @__PURE__ */
|
|
3025
|
+
/* @__PURE__ */ jsx8(
|
|
2902
3026
|
"circle",
|
|
2903
3027
|
{
|
|
2904
3028
|
cx,
|
|
@@ -2911,7 +3035,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2911
3035
|
onMouseDown: (e) => onAnswerPortDown(e, node.id, ans, cx, portRowY)
|
|
2912
3036
|
}
|
|
2913
3037
|
),
|
|
2914
|
-
/* @__PURE__ */
|
|
3038
|
+
/* @__PURE__ */ jsx8(
|
|
2915
3039
|
"path",
|
|
2916
3040
|
{
|
|
2917
3041
|
d: `M ${cx - 3} ${portRowY - 2} L ${cx} ${portRowY + 2} L ${cx + 3} ${portRowY - 2}`,
|
|
@@ -2928,7 +3052,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2928
3052
|
] });
|
|
2929
3053
|
}
|
|
2930
3054
|
function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, onEditChange, onEditCommit, onEditCancel, onDoubleClick, onContextMenu, onWaypointDown }) {
|
|
2931
|
-
const [hovered, setHovered] =
|
|
3055
|
+
const [hovered, setHovered] = useState8(false);
|
|
2932
3056
|
const from = nodes.find((n) => n.id === edge.from);
|
|
2933
3057
|
const to = nodes.find((n) => n.id === edge.to);
|
|
2934
3058
|
if (!from || !to) return null;
|
|
@@ -2967,7 +3091,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
2967
3091
|
const labelW = edge.label ? Math.max(60, Math.ceil(estimateTextW2(edge.label, 7) + 18)) : 60;
|
|
2968
3092
|
const showHandle = !!onWaypointDown && (hovered || !!wp);
|
|
2969
3093
|
const flowClass = dash ? void 0 : isAmber ? "edge-flow-amber" : "edge-flow";
|
|
2970
|
-
return /* @__PURE__ */
|
|
3094
|
+
return /* @__PURE__ */ jsxs8(
|
|
2971
3095
|
"g",
|
|
2972
3096
|
{
|
|
2973
3097
|
onDoubleClick: (e) => {
|
|
@@ -2980,8 +3104,8 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
2980
3104
|
onMouseEnter: () => setHovered(true),
|
|
2981
3105
|
onMouseLeave: () => setHovered(false),
|
|
2982
3106
|
children: [
|
|
2983
|
-
/* @__PURE__ */
|
|
2984
|
-
/* @__PURE__ */
|
|
3107
|
+
/* @__PURE__ */ jsx8("path", { d, fill: "none", stroke: "transparent", strokeWidth: 14, style: STYLE_EDGE_HIT }),
|
|
3108
|
+
/* @__PURE__ */ jsx8(
|
|
2985
3109
|
"path",
|
|
2986
3110
|
{
|
|
2987
3111
|
d,
|
|
@@ -2996,7 +3120,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
2996
3120
|
style: STYLE_NO_EVENTS
|
|
2997
3121
|
}
|
|
2998
3122
|
),
|
|
2999
|
-
showHandle && /* @__PURE__ */
|
|
3123
|
+
showHandle && /* @__PURE__ */ jsx8(
|
|
3000
3124
|
"circle",
|
|
3001
3125
|
{
|
|
3002
3126
|
cx: hx,
|
|
@@ -3012,7 +3136,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3012
3136
|
}
|
|
3013
3137
|
}
|
|
3014
3138
|
),
|
|
3015
|
-
editing && !isAmber ? /* @__PURE__ */
|
|
3139
|
+
editing && !isAmber ? /* @__PURE__ */ jsx8("foreignObject", { x: mx - labelW / 2, y: my - 12, width: labelW, height: 22, children: /* @__PURE__ */ jsx8(
|
|
3016
3140
|
"input",
|
|
3017
3141
|
{
|
|
3018
3142
|
autoFocus: true,
|
|
@@ -3046,8 +3170,8 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3046
3170
|
fontFamily: "inherit"
|
|
3047
3171
|
}
|
|
3048
3172
|
}
|
|
3049
|
-
) }) : edge.label && !isAmber ? /* @__PURE__ */
|
|
3050
|
-
/* @__PURE__ */
|
|
3173
|
+
) }) : edge.label && !isAmber ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
3174
|
+
/* @__PURE__ */ jsx8(
|
|
3051
3175
|
"rect",
|
|
3052
3176
|
{
|
|
3053
3177
|
x: mx - labelW / 2,
|
|
@@ -3061,7 +3185,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3061
3185
|
style: STYLE_EDGE_LABEL_HIT
|
|
3062
3186
|
}
|
|
3063
3187
|
),
|
|
3064
|
-
/* @__PURE__ */
|
|
3188
|
+
/* @__PURE__ */ jsx8(
|
|
3065
3189
|
"text",
|
|
3066
3190
|
{
|
|
3067
3191
|
x: mx,
|
|
@@ -3082,8 +3206,8 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3082
3206
|
}
|
|
3083
3207
|
|
|
3084
3208
|
// src/ui/Minimap.tsx
|
|
3085
|
-
import { useCallback as
|
|
3086
|
-
import { jsx as
|
|
3209
|
+
import { useCallback as useCallback6, useRef as useRef4 } from "react";
|
|
3210
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3087
3211
|
var W = 168;
|
|
3088
3212
|
var H = 112;
|
|
3089
3213
|
var PAD = 18;
|
|
@@ -3131,7 +3255,7 @@ function Minimap({
|
|
|
3131
3255
|
x: (mx - offsetX) / scale,
|
|
3132
3256
|
y: (my - offsetY) / scale
|
|
3133
3257
|
});
|
|
3134
|
-
const panTo =
|
|
3258
|
+
const panTo = useCallback6((e) => {
|
|
3135
3259
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
3136
3260
|
const mx = e.clientX - rect.left;
|
|
3137
3261
|
const my = e.clientY - rect.top;
|
|
@@ -3163,9 +3287,11 @@ function Minimap({
|
|
|
3163
3287
|
w: Math.max(2, Math.min(W, vp2.x) - Math.max(0, vp1.x)),
|
|
3164
3288
|
h: Math.max(2, Math.min(H, vp2.y) - Math.max(0, vp1.y))
|
|
3165
3289
|
};
|
|
3166
|
-
return /* @__PURE__ */
|
|
3290
|
+
return /* @__PURE__ */ jsx9(
|
|
3167
3291
|
"div",
|
|
3168
3292
|
{
|
|
3293
|
+
"aria-label": "Minimap \u2014 click to re-center the viewport",
|
|
3294
|
+
role: "img",
|
|
3169
3295
|
style: {
|
|
3170
3296
|
position: "absolute",
|
|
3171
3297
|
bottom: 14,
|
|
@@ -3177,7 +3303,7 @@ function Minimap({
|
|
|
3177
3303
|
boxShadow: isDark ? "0 8px 20px rgba(0,0,0,0.45)" : "0 6px 18px rgba(15,23,42,0.08)",
|
|
3178
3304
|
backdropFilter: "blur(6px)"
|
|
3179
3305
|
},
|
|
3180
|
-
children: /* @__PURE__ */
|
|
3306
|
+
children: /* @__PURE__ */ jsxs9(
|
|
3181
3307
|
"svg",
|
|
3182
3308
|
{
|
|
3183
3309
|
width: W,
|
|
@@ -3188,10 +3314,10 @@ function Minimap({
|
|
|
3188
3314
|
onMouseUp,
|
|
3189
3315
|
onMouseLeave: onMouseUp,
|
|
3190
3316
|
children: [
|
|
3191
|
-
/* @__PURE__ */
|
|
3317
|
+
/* @__PURE__ */ jsx9("rect", { width: W, height: H, rx: 6, fill: isDark ? "#0f172a" : "#fafbfc" }),
|
|
3192
3318
|
boxes.map((b) => {
|
|
3193
3319
|
const p = project(b.x, b.y);
|
|
3194
|
-
return /* @__PURE__ */
|
|
3320
|
+
return /* @__PURE__ */ jsx9(
|
|
3195
3321
|
"rect",
|
|
3196
3322
|
{
|
|
3197
3323
|
x: p.x,
|
|
@@ -3204,7 +3330,7 @@ function Minimap({
|
|
|
3204
3330
|
b.id
|
|
3205
3331
|
);
|
|
3206
3332
|
}),
|
|
3207
|
-
/* @__PURE__ */
|
|
3333
|
+
/* @__PURE__ */ jsx9(
|
|
3208
3334
|
"rect",
|
|
3209
3335
|
{
|
|
3210
3336
|
x: vpRect.x,
|
|
@@ -3225,8 +3351,8 @@ function Minimap({
|
|
|
3225
3351
|
}
|
|
3226
3352
|
|
|
3227
3353
|
// src/ui/ContextMenu.tsx
|
|
3228
|
-
import { useEffect as useEffect6, useRef as useRef5, useState as
|
|
3229
|
-
import { Fragment as Fragment3, jsx as
|
|
3354
|
+
import { useEffect as useEffect6, useRef as useRef5, useState as useState9 } from "react";
|
|
3355
|
+
import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3230
3356
|
function ContextMenu({
|
|
3231
3357
|
x,
|
|
3232
3358
|
y,
|
|
@@ -3256,7 +3382,7 @@ function ContextMenu({
|
|
|
3256
3382
|
containerRef
|
|
3257
3383
|
}) {
|
|
3258
3384
|
const menuRef = useRef5(null);
|
|
3259
|
-
const [pos, setPos] =
|
|
3385
|
+
const [pos, setPos] = useState9({ x, y });
|
|
3260
3386
|
useEffect6(() => {
|
|
3261
3387
|
if (!menuRef.current || !containerRef.current) return;
|
|
3262
3388
|
const m = menuRef.current.getBoundingClientRect();
|
|
@@ -3272,7 +3398,7 @@ function ContextMenu({
|
|
|
3272
3398
|
const dividerColor = isDark ? "#334155" : "#f1f5f9";
|
|
3273
3399
|
const text = t.textPrimary;
|
|
3274
3400
|
const muted = t.textMuted;
|
|
3275
|
-
const item = (label, onClick, color, disabled) => /* @__PURE__ */
|
|
3401
|
+
const item = (label, onClick, color, disabled) => /* @__PURE__ */ jsx10(
|
|
3276
3402
|
"button",
|
|
3277
3403
|
{
|
|
3278
3404
|
onClick: disabled ? void 0 : onClick,
|
|
@@ -3302,8 +3428,8 @@ function ContextMenu({
|
|
|
3302
3428
|
},
|
|
3303
3429
|
label
|
|
3304
3430
|
);
|
|
3305
|
-
const divider2 = /* @__PURE__ */
|
|
3306
|
-
return /* @__PURE__ */
|
|
3431
|
+
const divider2 = /* @__PURE__ */ jsx10("div", { style: { height: 1, background: dividerColor, margin: "4px 0" } });
|
|
3432
|
+
return /* @__PURE__ */ jsx10(
|
|
3307
3433
|
"div",
|
|
3308
3434
|
{
|
|
3309
3435
|
ref: menuRef,
|
|
@@ -3321,30 +3447,30 @@ function ContextMenu({
|
|
|
3321
3447
|
boxShadow: isDark ? "0 8px 32px rgba(0,0,0,0.5)" : "0 8px 32px rgba(0,0,0,0.12)",
|
|
3322
3448
|
fontFamily: "ui-sans-serif,system-ui,sans-serif"
|
|
3323
3449
|
},
|
|
3324
|
-
children: edgeId ? /* @__PURE__ */
|
|
3325
|
-
/* @__PURE__ */
|
|
3450
|
+
children: edgeId ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
3451
|
+
/* @__PURE__ */ jsx10("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Edge" }),
|
|
3326
3452
|
item("Rename label (dbl-click)", () => onEdgeRename?.()),
|
|
3327
3453
|
divider2,
|
|
3328
|
-
/* @__PURE__ */
|
|
3454
|
+
/* @__PURE__ */ jsx10("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Style" }),
|
|
3329
3455
|
item(`Solid${currentEdgeStyle === "solid" || !currentEdgeStyle ? " \u2713" : ""}`, () => onEdgeStyle?.("solid")),
|
|
3330
3456
|
item(`Dashed${currentEdgeStyle === "dashed" ? " \u2713" : ""}`, () => onEdgeStyle?.("dashed")),
|
|
3331
3457
|
item(`Dotted${currentEdgeStyle === "dotted" ? " \u2713" : ""}`, () => onEdgeStyle?.("dotted")),
|
|
3332
3458
|
divider2,
|
|
3333
|
-
/* @__PURE__ */
|
|
3459
|
+
/* @__PURE__ */ jsx10("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Arrowhead" }),
|
|
3334
3460
|
item(`Arrow${currentEdgeArrow !== "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("arrow")),
|
|
3335
3461
|
item(`None${currentEdgeArrow === "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("none")),
|
|
3336
3462
|
divider2,
|
|
3337
3463
|
item("Reset routing", () => onEdgeResetRouting?.(), void 0, !edgeHasWaypoint),
|
|
3338
3464
|
item("Delete edge", () => onEdgeDelete?.(), "#ef4444")
|
|
3339
|
-
] }) : nodeId ? /* @__PURE__ */
|
|
3340
|
-
/* @__PURE__ */
|
|
3465
|
+
] }) : nodeId ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
3466
|
+
/* @__PURE__ */ jsx10("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Node" }),
|
|
3341
3467
|
item("Rename (dbl-click)", onRename),
|
|
3342
3468
|
item("Duplicate", onDuplicate),
|
|
3343
3469
|
item("Disconnect all edges", onDisconnect),
|
|
3344
3470
|
divider2,
|
|
3345
3471
|
item("Delete node", onDelete, "#ef4444")
|
|
3346
|
-
] }) : /* @__PURE__ */
|
|
3347
|
-
/* @__PURE__ */
|
|
3472
|
+
] }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
3473
|
+
/* @__PURE__ */ jsx10("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Canvas" }),
|
|
3348
3474
|
item("Add node here", onAddNode, acc.color),
|
|
3349
3475
|
item("Re-center (Ctrl+0)", onReCenter),
|
|
3350
3476
|
divider2,
|
|
@@ -3356,7 +3482,7 @@ function ContextMenu({
|
|
|
3356
3482
|
}
|
|
3357
3483
|
|
|
3358
3484
|
// src/ui/DiagramCanvas.tsx
|
|
3359
|
-
import { Fragment as Fragment4, jsx as
|
|
3485
|
+
import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3360
3486
|
var STYLE_LABEL2 = { pointerEvents: "none", userSelect: "none" };
|
|
3361
3487
|
var STYLE_LIVE_PORT = { opacity: 0.85, pointerEvents: "none" };
|
|
3362
3488
|
var STYLE_NODE_GRAB = { cursor: "grab" };
|
|
@@ -3433,8 +3559,8 @@ function DiagramCanvas(props) {
|
|
|
3433
3559
|
onCtxEdgeDelete,
|
|
3434
3560
|
onCtxEdgeResetRouting
|
|
3435
3561
|
} = props;
|
|
3436
|
-
return /* @__PURE__ */
|
|
3437
|
-
/* @__PURE__ */
|
|
3562
|
+
return /* @__PURE__ */ jsxs11("div", { ref: containerRef, style: { flex: 1, overflow: "hidden", position: "relative", background: t.canvas }, children: [
|
|
3563
|
+
/* @__PURE__ */ jsxs11(
|
|
3438
3564
|
"svg",
|
|
3439
3565
|
{
|
|
3440
3566
|
ref: svgRef,
|
|
@@ -3450,8 +3576,8 @@ function DiagramCanvas(props) {
|
|
|
3450
3576
|
onMouseLeave: onMouseUp,
|
|
3451
3577
|
onContextMenu: onSvgContextMenu,
|
|
3452
3578
|
children: [
|
|
3453
|
-
/* @__PURE__ */
|
|
3454
|
-
/* @__PURE__ */
|
|
3579
|
+
/* @__PURE__ */ jsxs11("defs", { children: [
|
|
3580
|
+
/* @__PURE__ */ jsx11("style", { children: reducedMotion ? `
|
|
3455
3581
|
.edge-flow { stroke-dasharray: 0; }
|
|
3456
3582
|
.edge-flow-amber { stroke-dasharray: 0; }
|
|
3457
3583
|
.edge-live { stroke-dasharray: 4 4; }
|
|
@@ -3462,15 +3588,15 @@ function DiagramCanvas(props) {
|
|
|
3462
3588
|
.edge-flow-amber { stroke-dasharray: 6 4; animation: edgeFlowFast 0.65s linear infinite; }
|
|
3463
3589
|
.edge-live { stroke-dasharray: 7 5; animation: edgeFlow 0.55s linear infinite; }
|
|
3464
3590
|
` }),
|
|
3465
|
-
/* @__PURE__ */
|
|
3466
|
-
/* @__PURE__ */
|
|
3467
|
-
/* @__PURE__ */
|
|
3468
|
-
/* @__PURE__ */
|
|
3469
|
-
/* @__PURE__ */
|
|
3591
|
+
/* @__PURE__ */ jsx11("pattern", { id: "dots", width: GRID, height: GRID, patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx11("circle", { cx: GRID / 2, cy: GRID / 2, r: 1.1, fill: t.dot }) }),
|
|
3592
|
+
/* @__PURE__ */ jsx11("filter", { id: "nodeShadow", x: "-25%", y: "-25%", width: "150%", height: "160%", children: /* @__PURE__ */ jsx11("feDropShadow", { dx: "0", dy: "3", stdDeviation: "5", floodColor: shadowClr, floodOpacity: "1" }) }),
|
|
3593
|
+
/* @__PURE__ */ jsx11("marker", { id: "arrowhead", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ jsx11("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: arrowClr }) }),
|
|
3594
|
+
/* @__PURE__ */ jsx11("marker", { id: "arrowAmber", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ jsx11("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: amberArrow }) }),
|
|
3595
|
+
/* @__PURE__ */ jsx11("marker", { id: "arrowLive", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ jsx11("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: acc.color }) })
|
|
3470
3596
|
] }),
|
|
3471
|
-
/* @__PURE__ */
|
|
3472
|
-
/* @__PURE__ */
|
|
3473
|
-
model.edges.map((e) => /* @__PURE__ */
|
|
3597
|
+
/* @__PURE__ */ jsx11("rect", { width: "100%", height: "100%", fill: "url(#dots)", "data-bg": "1" }),
|
|
3598
|
+
/* @__PURE__ */ jsxs11("g", { transform: `translate(${transform.x},${transform.y}) scale(${transform.scale})`, children: [
|
|
3599
|
+
model.edges.map((e) => /* @__PURE__ */ jsx11(
|
|
3474
3600
|
EdgeLine,
|
|
3475
3601
|
{
|
|
3476
3602
|
edge: e,
|
|
@@ -3492,9 +3618,9 @@ function DiagramCanvas(props) {
|
|
|
3492
3618
|
)),
|
|
3493
3619
|
liveEdge && (() => {
|
|
3494
3620
|
const d = bezierPath2(liveEdge.fromX, liveEdge.fromY, liveEdge.toX, liveEdge.toY, liveEdge.exitDir);
|
|
3495
|
-
return /* @__PURE__ */
|
|
3621
|
+
return /* @__PURE__ */ jsx11("path", { d, fill: "none", stroke: acc.color, strokeWidth: 2, strokeLinecap: "round", className: "edge-live", opacity: 0.8, markerEnd: "url(#arrowLive)" });
|
|
3496
3622
|
})(),
|
|
3497
|
-
alignGuides?.x && /* @__PURE__ */
|
|
3623
|
+
alignGuides?.x && /* @__PURE__ */ jsx11(
|
|
3498
3624
|
"line",
|
|
3499
3625
|
{
|
|
3500
3626
|
x1: alignGuides.x.pos,
|
|
@@ -3508,7 +3634,7 @@ function DiagramCanvas(props) {
|
|
|
3508
3634
|
pointerEvents: "none"
|
|
3509
3635
|
}
|
|
3510
3636
|
),
|
|
3511
|
-
alignGuides?.y && /* @__PURE__ */
|
|
3637
|
+
alignGuides?.y && /* @__PURE__ */ jsx11(
|
|
3512
3638
|
"line",
|
|
3513
3639
|
{
|
|
3514
3640
|
y1: alignGuides.y.pos,
|
|
@@ -3527,11 +3653,12 @@ function DiagramCanvas(props) {
|
|
|
3527
3653
|
const isQuestion2 = variant === "question";
|
|
3528
3654
|
const { w: nW } = nodeDims(node, variant);
|
|
3529
3655
|
const isSelected = selectedSet.has(node.id);
|
|
3530
|
-
return /* @__PURE__ */
|
|
3656
|
+
return /* @__PURE__ */ jsxs11(
|
|
3531
3657
|
"g",
|
|
3532
3658
|
{
|
|
3533
3659
|
transform: `translate(${node.x ?? 0},${node.y ?? 0})`,
|
|
3534
3660
|
role: "button",
|
|
3661
|
+
tabIndex: 0,
|
|
3535
3662
|
"aria-label": `${variantLabel} ${variant === "journey" ? idx + 1 + ": " : ""}${node.label}${isSelected ? ", selected" : ""}`,
|
|
3536
3663
|
style: drag?.nodeId === node.id ? STYLE_NODE_GRABBING : STYLE_NODE_GRAB,
|
|
3537
3664
|
onMouseDown: (e) => onNodeMouseDown(e, node.id),
|
|
@@ -3540,11 +3667,20 @@ function DiagramCanvas(props) {
|
|
|
3540
3667
|
onContextMenu: (e) => onNodeContextMenu(e, node.id),
|
|
3541
3668
|
onMouseEnter: () => setHoveredId(node.id),
|
|
3542
3669
|
onMouseLeave: () => setHoveredId(null),
|
|
3670
|
+
onFocus: () => setHoveredId(node.id),
|
|
3671
|
+
onBlur: () => setHoveredId(null),
|
|
3672
|
+
onKeyDown: (e) => {
|
|
3673
|
+
if (e.key === "F2" || e.key === "Enter" && !e.ctrlKey && !e.metaKey) {
|
|
3674
|
+
e.preventDefault();
|
|
3675
|
+
setEditingId(node.id);
|
|
3676
|
+
setEditLabel(node.label);
|
|
3677
|
+
}
|
|
3678
|
+
},
|
|
3543
3679
|
children: [
|
|
3544
|
-
/* @__PURE__ */
|
|
3545
|
-
isQuestion2 ? /* @__PURE__ */
|
|
3546
|
-
/* @__PURE__ */
|
|
3547
|
-
editingId === node.id ? /* @__PURE__ */
|
|
3680
|
+
/* @__PURE__ */ jsx11("title", { children: `${variantLabel}: ${node.label}` }),
|
|
3681
|
+
isQuestion2 ? /* @__PURE__ */ jsx11(QuestionNode, { node, selected: isSelected, edges: model.edges, isDark, onAnswerPortDown, qW: nW }) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
3682
|
+
/* @__PURE__ */ jsx11(NodeShape, { node, selected: isSelected, variant, stepNumber: variant === "journey" ? idx + 1 : void 0, t, isDark, w: nW }),
|
|
3683
|
+
editingId === node.id ? /* @__PURE__ */ jsx11("foreignObject", { x: 6, y: 6, width: nW - 12, height: NODE_H2 - 12, children: /* @__PURE__ */ jsx11(
|
|
3548
3684
|
"input",
|
|
3549
3685
|
{
|
|
3550
3686
|
autoFocus: true,
|
|
@@ -3557,8 +3693,8 @@ function DiagramCanvas(props) {
|
|
|
3557
3693
|
},
|
|
3558
3694
|
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 }
|
|
3559
3695
|
}
|
|
3560
|
-
) }) : /* @__PURE__ */
|
|
3561
|
-
/* @__PURE__ */
|
|
3696
|
+
) }) : /* @__PURE__ */ jsx11("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 }),
|
|
3697
|
+
/* @__PURE__ */ jsx11(
|
|
3562
3698
|
"circle",
|
|
3563
3699
|
{
|
|
3564
3700
|
cx: nW / 2,
|
|
@@ -3572,7 +3708,7 @@ function DiagramCanvas(props) {
|
|
|
3572
3708
|
}
|
|
3573
3709
|
)
|
|
3574
3710
|
] }),
|
|
3575
|
-
liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */
|
|
3711
|
+
liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */ jsx11("circle", { cx: nW / 2, cy: -1, r: portR, fill: acc.color, stroke: isDark ? "#0f172a" : "white", strokeWidth: 2, style: STYLE_LIVE_PORT })
|
|
3576
3712
|
]
|
|
3577
3713
|
},
|
|
3578
3714
|
node.id
|
|
@@ -3588,7 +3724,7 @@ function DiagramCanvas(props) {
|
|
|
3588
3724
|
const top = Math.min(boxSel.sy, boxSel.cy) - rect.top;
|
|
3589
3725
|
const w = Math.abs(boxSel.cx - boxSel.sx);
|
|
3590
3726
|
const h = Math.abs(boxSel.cy - boxSel.sy);
|
|
3591
|
-
return /* @__PURE__ */
|
|
3727
|
+
return /* @__PURE__ */ jsx11(
|
|
3592
3728
|
"div",
|
|
3593
3729
|
{
|
|
3594
3730
|
style: {
|
|
@@ -3605,18 +3741,18 @@ function DiagramCanvas(props) {
|
|
|
3605
3741
|
}
|
|
3606
3742
|
);
|
|
3607
3743
|
})(),
|
|
3608
|
-
model.nodes.length === 0 && /* @__PURE__ */
|
|
3609
|
-
/* @__PURE__ */
|
|
3610
|
-
/* @__PURE__ */
|
|
3744
|
+
model.nodes.length === 0 && /* @__PURE__ */ jsxs11("div", { style: { position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", pointerEvents: "none", gap: 8 }, children: [
|
|
3745
|
+
/* @__PURE__ */ jsx11("div", { style: { fontSize: 36, opacity: 0.1, color: t.textPrimary }, children: variant === "question" ? "?" : variant === "journey" ? "\u2197" : "\u2B21" }),
|
|
3746
|
+
/* @__PURE__ */ jsxs11("div", { style: { fontSize: 13, color: t.textMuted, fontWeight: 500 }, children: [
|
|
3611
3747
|
"Click ",
|
|
3612
|
-
/* @__PURE__ */
|
|
3748
|
+
/* @__PURE__ */ jsxs11("strong", { style: { color: acc.color }, children: [
|
|
3613
3749
|
"+ ",
|
|
3614
3750
|
variantLabel
|
|
3615
3751
|
] }),
|
|
3616
3752
|
" to start"
|
|
3617
3753
|
] })
|
|
3618
3754
|
] }),
|
|
3619
|
-
model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */
|
|
3755
|
+
model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */ jsx11(
|
|
3620
3756
|
Minimap,
|
|
3621
3757
|
{
|
|
3622
3758
|
model,
|
|
@@ -3631,7 +3767,7 @@ function DiagramCanvas(props) {
|
|
|
3631
3767
|
}
|
|
3632
3768
|
}
|
|
3633
3769
|
),
|
|
3634
|
-
ctxMenu && /* @__PURE__ */
|
|
3770
|
+
ctxMenu && /* @__PURE__ */ jsx11(
|
|
3635
3771
|
ContextMenu,
|
|
3636
3772
|
{
|
|
3637
3773
|
x: ctxMenu.x,
|
|
@@ -3666,22 +3802,22 @@ function DiagramCanvas(props) {
|
|
|
3666
3802
|
}
|
|
3667
3803
|
|
|
3668
3804
|
// src/ui/hooks/useHistory.ts
|
|
3669
|
-
import { useCallback as
|
|
3805
|
+
import { useCallback as useCallback7, useRef as useRef6, useState as useState10 } from "react";
|
|
3670
3806
|
var MAX_HISTORY = 80;
|
|
3671
3807
|
function useHistory(initial, onChange) {
|
|
3672
|
-
const [state, setState] =
|
|
3808
|
+
const [state, setState] = useState10(initial);
|
|
3673
3809
|
const stackRef = useRef6([initial]);
|
|
3674
3810
|
const idxRef = useRef6(0);
|
|
3675
|
-
const [, setTick] =
|
|
3811
|
+
const [, setTick] = useState10(0);
|
|
3676
3812
|
const bump = () => setTick((n) => n + 1);
|
|
3677
|
-
const apply =
|
|
3813
|
+
const apply = useCallback7(
|
|
3678
3814
|
(next) => {
|
|
3679
3815
|
setState(next);
|
|
3680
3816
|
onChange?.(next);
|
|
3681
3817
|
},
|
|
3682
3818
|
[onChange]
|
|
3683
3819
|
);
|
|
3684
|
-
const applyAndPush =
|
|
3820
|
+
const applyAndPush = useCallback7(
|
|
3685
3821
|
(next) => {
|
|
3686
3822
|
const stack = stackRef.current.slice(0, idxRef.current + 1);
|
|
3687
3823
|
stack.push(next);
|
|
@@ -3694,7 +3830,7 @@ function useHistory(initial, onChange) {
|
|
|
3694
3830
|
},
|
|
3695
3831
|
[onChange]
|
|
3696
3832
|
);
|
|
3697
|
-
const undo =
|
|
3833
|
+
const undo = useCallback7(() => {
|
|
3698
3834
|
if (idxRef.current <= 0) return;
|
|
3699
3835
|
idxRef.current--;
|
|
3700
3836
|
const next = stackRef.current[idxRef.current];
|
|
@@ -3702,7 +3838,7 @@ function useHistory(initial, onChange) {
|
|
|
3702
3838
|
onChange?.(next);
|
|
3703
3839
|
bump();
|
|
3704
3840
|
}, [onChange]);
|
|
3705
|
-
const redo =
|
|
3841
|
+
const redo = useCallback7(() => {
|
|
3706
3842
|
if (idxRef.current >= stackRef.current.length - 1) return;
|
|
3707
3843
|
idxRef.current++;
|
|
3708
3844
|
const next = stackRef.current[idxRef.current];
|
|
@@ -3863,9 +3999,9 @@ function useCanvasTouch(ref, {
|
|
|
3863
3999
|
}
|
|
3864
4000
|
|
|
3865
4001
|
// src/ui/hooks/useElementSize.ts
|
|
3866
|
-
import { useEffect as useEffect9, useState as
|
|
4002
|
+
import { useEffect as useEffect9, useState as useState11 } from "react";
|
|
3867
4003
|
function useElementSize(ref) {
|
|
3868
|
-
const [size, setSize] =
|
|
4004
|
+
const [size, setSize] = useState11({ w: 0, h: 0 });
|
|
3869
4005
|
useEffect9(() => {
|
|
3870
4006
|
const el = ref.current;
|
|
3871
4007
|
if (!el || typeof ResizeObserver === "undefined") return;
|
|
@@ -3972,12 +4108,12 @@ function nearestInDirection(fromX, fromY, dir, candidates) {
|
|
|
3972
4108
|
}
|
|
3973
4109
|
|
|
3974
4110
|
// src/ui/DiagramEditor.tsx
|
|
3975
|
-
import { Fragment as Fragment5, jsx as
|
|
4111
|
+
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3976
4112
|
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 };
|
|
3977
4113
|
var STYLE_FLEX_ROW = { flex: 1, display: "flex", overflow: "hidden" };
|
|
3978
4114
|
function DiagramEditor(props) {
|
|
3979
4115
|
if (props.initialModel?.type === "sequence") {
|
|
3980
|
-
return /* @__PURE__ */
|
|
4116
|
+
return /* @__PURE__ */ jsx12(
|
|
3981
4117
|
SequenceEditor,
|
|
3982
4118
|
{
|
|
3983
4119
|
initialModel: props.initialModel,
|
|
@@ -3991,7 +4127,7 @@ function DiagramEditor(props) {
|
|
|
3991
4127
|
}
|
|
3992
4128
|
);
|
|
3993
4129
|
}
|
|
3994
|
-
return /* @__PURE__ */
|
|
4130
|
+
return /* @__PURE__ */ jsx12(FlowchartEditor, { ...props });
|
|
3995
4131
|
}
|
|
3996
4132
|
function FlowchartEditor({
|
|
3997
4133
|
initialModel,
|
|
@@ -4005,25 +4141,26 @@ function FlowchartEditor({
|
|
|
4005
4141
|
themeOverrides
|
|
4006
4142
|
}) {
|
|
4007
4143
|
const base = initialModel ? { ...initialModel, variant: initialModel.variant ?? variant } : presetFlowchartModel(variant);
|
|
4008
|
-
const notify =
|
|
4144
|
+
const notify = useCallback8((m) => onChange?.(m), [onChange]);
|
|
4009
4145
|
const history = useHistory(base, notify);
|
|
4010
4146
|
const { state: model, apply: applyModel, applyAndPush, undo, redo } = history;
|
|
4011
|
-
const
|
|
4012
|
-
const [
|
|
4013
|
-
const [
|
|
4014
|
-
const [
|
|
4015
|
-
const [
|
|
4016
|
-
const [
|
|
4017
|
-
const [
|
|
4018
|
-
const [
|
|
4019
|
-
const [
|
|
4147
|
+
const { toasts, showToast, dismissToast } = useToast();
|
|
4148
|
+
const [transform, setTransform] = useState12({ x: 60, y: 60, scale: 1 });
|
|
4149
|
+
const [selected, setSelected] = useState12(null);
|
|
4150
|
+
const [selectedSet, setSelectedSet] = useState12(() => /* @__PURE__ */ new Set());
|
|
4151
|
+
const [drag, setDrag] = useState12(null);
|
|
4152
|
+
const [pan, setPan] = useState12(null);
|
|
4153
|
+
const [boxSel, setBoxSel] = useState12(null);
|
|
4154
|
+
const [liveEdge, setLiveEdge] = useState12(null);
|
|
4155
|
+
const [alignGuides, setAlignGuides] = useState12(null);
|
|
4156
|
+
const [waypointDrag, setWaypointDrag] = useState12(null);
|
|
4020
4157
|
const groupDragOriginsRef = useRef7(null);
|
|
4021
4158
|
const clipboardRef = useRef7(null);
|
|
4022
|
-
const selectOne =
|
|
4159
|
+
const selectOne = useCallback8((id) => {
|
|
4023
4160
|
setSelected(id);
|
|
4024
4161
|
setSelectedSet(id ? /* @__PURE__ */ new Set([id]) : /* @__PURE__ */ new Set());
|
|
4025
4162
|
}, []);
|
|
4026
|
-
const toggleSelect =
|
|
4163
|
+
const toggleSelect = useCallback8((id) => {
|
|
4027
4164
|
setSelectedSet((prev) => {
|
|
4028
4165
|
const next = new Set(prev);
|
|
4029
4166
|
if (next.has(id)) {
|
|
@@ -4037,18 +4174,18 @@ function FlowchartEditor({
|
|
|
4037
4174
|
return next;
|
|
4038
4175
|
});
|
|
4039
4176
|
}, []);
|
|
4040
|
-
const clearSelection =
|
|
4177
|
+
const clearSelection = useCallback8(() => {
|
|
4041
4178
|
setSelected(null);
|
|
4042
4179
|
setSelectedSet(/* @__PURE__ */ new Set());
|
|
4043
4180
|
}, []);
|
|
4044
|
-
const [editingId, setEditingId] =
|
|
4045
|
-
const [editLabel, setEditLabel] =
|
|
4046
|
-
const [editingEdgeId, setEditingEdgeId] =
|
|
4047
|
-
const [editEdgeLabel, setEditEdgeLabel] =
|
|
4048
|
-
const [hoveredId, setHoveredId] =
|
|
4049
|
-
const [ctxMenu, setCtxMenu] =
|
|
4050
|
-
const [navOpen, setNavOpen] =
|
|
4051
|
-
const [announcement, setAnnouncement] =
|
|
4181
|
+
const [editingId, setEditingId] = useState12(null);
|
|
4182
|
+
const [editLabel, setEditLabel] = useState12("");
|
|
4183
|
+
const [editingEdgeId, setEditingEdgeId] = useState12(null);
|
|
4184
|
+
const [editEdgeLabel, setEditEdgeLabel] = useState12("");
|
|
4185
|
+
const [hoveredId, setHoveredId] = useState12(null);
|
|
4186
|
+
const [ctxMenu, setCtxMenu] = useState12(null);
|
|
4187
|
+
const [navOpen, setNavOpen] = useState12(true);
|
|
4188
|
+
const [announcement, setAnnouncement] = useState12("");
|
|
4052
4189
|
const svgRef = useRef7(null);
|
|
4053
4190
|
const containerRef = useRef7(null);
|
|
4054
4191
|
const reducedMotion = usePrefersReducedMotion();
|
|
@@ -4056,7 +4193,7 @@ function FlowchartEditor({
|
|
|
4056
4193
|
const isCoarse = useIsCoarsePointer();
|
|
4057
4194
|
const portR = isCoarse ? 9 : 6;
|
|
4058
4195
|
const viewport = useElementSize(svgRef);
|
|
4059
|
-
const reCenter =
|
|
4196
|
+
const reCenter = useCallback8(() => {
|
|
4060
4197
|
if (!svgRef.current) return;
|
|
4061
4198
|
const rect = svgRef.current.getBoundingClientRect();
|
|
4062
4199
|
const W2 = rect.width, H2 = rect.height;
|
|
@@ -4080,7 +4217,7 @@ function FlowchartEditor({
|
|
|
4080
4217
|
const cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;
|
|
4081
4218
|
setTransform({ scale, x: W2 / 2 - cx * scale, y: H2 / 2 - cy * scale });
|
|
4082
4219
|
}, [model.nodes, variant]);
|
|
4083
|
-
const jumpToNode =
|
|
4220
|
+
const jumpToNode = useCallback8((nodeId) => {
|
|
4084
4221
|
const node = model.nodes.find((n) => n.id === nodeId);
|
|
4085
4222
|
if (!node || !svgRef.current) return;
|
|
4086
4223
|
const rect = svgRef.current.getBoundingClientRect();
|
|
@@ -4091,7 +4228,7 @@ function FlowchartEditor({
|
|
|
4091
4228
|
setTransform({ scale, x: rect.width / 2 - cx * scale, y: rect.height / 2 - cy * scale });
|
|
4092
4229
|
selectOne(nodeId);
|
|
4093
4230
|
}, [model.nodes, variant, transform.scale, selectOne]);
|
|
4094
|
-
const duplicateIds =
|
|
4231
|
+
const duplicateIds = useCallback8((ids) => {
|
|
4095
4232
|
if (ids.length === 0) return;
|
|
4096
4233
|
const idSet = new Set(ids);
|
|
4097
4234
|
const idMap = /* @__PURE__ */ new Map();
|
|
@@ -4123,7 +4260,7 @@ function FlowchartEditor({
|
|
|
4123
4260
|
setSelected(newIds[newIds.length - 1] ?? null);
|
|
4124
4261
|
setSelectedSet(new Set(newIds));
|
|
4125
4262
|
}, [model, applyAndPush]);
|
|
4126
|
-
const duplicateNode =
|
|
4263
|
+
const duplicateNode = useCallback8((nodeId) => {
|
|
4127
4264
|
duplicateIds([nodeId]);
|
|
4128
4265
|
}, [duplicateIds]);
|
|
4129
4266
|
useEffect10(() => {
|
|
@@ -4258,12 +4395,12 @@ function FlowchartEditor({
|
|
|
4258
4395
|
}
|
|
4259
4396
|
];
|
|
4260
4397
|
useEditorKeyboard(keyCommands, [undo, redo, reCenter, selected, selectedSet, ctxMenu, liveEdge, editingId, boxSel, model, applyAndPush, duplicateNode, clearSelection]);
|
|
4261
|
-
const toCanvas =
|
|
4398
|
+
const toCanvas = useCallback8((clientX, clientY) => {
|
|
4262
4399
|
const rect = svgRef.current.getBoundingClientRect();
|
|
4263
4400
|
return { x: (clientX - rect.left - transform.x) / transform.scale, y: (clientY - rect.top - transform.y) / transform.scale };
|
|
4264
4401
|
}, [transform]);
|
|
4265
4402
|
useCanvasWheel(svgRef, setTransform);
|
|
4266
|
-
const onCanvasLongPress =
|
|
4403
|
+
const onCanvasLongPress = useCallback8((x, y) => {
|
|
4267
4404
|
setCtxMenu({ x, y, nodeId: null });
|
|
4268
4405
|
}, []);
|
|
4269
4406
|
useCanvasTouch(svgRef, { transform, setTransform, onLongPress: onCanvasLongPress });
|
|
@@ -4534,8 +4671,8 @@ function FlowchartEditor({
|
|
|
4534
4671
|
};
|
|
4535
4672
|
applyAndPush(updated);
|
|
4536
4673
|
};
|
|
4537
|
-
const handleExport = useExporters(model, onExport, "diagram");
|
|
4538
|
-
const positionFlowchartNodes =
|
|
4674
|
+
const handleExport = useExporters(model, onExport, "diagram", (msg) => showToast(msg, "success"));
|
|
4675
|
+
const positionFlowchartNodes = useCallback8((m) => ({
|
|
4539
4676
|
...m,
|
|
4540
4677
|
nodes: m.nodes.map((n, i) => ({
|
|
4541
4678
|
...n,
|
|
@@ -4543,14 +4680,19 @@ function FlowchartEditor({
|
|
|
4543
4680
|
y: n.y ?? snap(80 + Math.floor(i / 4) * 140)
|
|
4544
4681
|
}))
|
|
4545
4682
|
}), []);
|
|
4546
|
-
const handleImport = useImporter(applyAndPush, {
|
|
4683
|
+
const handleImport = useImporter(applyAndPush, {
|
|
4684
|
+
transform: positionFlowchartNodes,
|
|
4685
|
+
onSuccess: (msg) => showToast(msg, "success"),
|
|
4686
|
+
onError: (msg) => showToast(msg, "error")
|
|
4687
|
+
});
|
|
4547
4688
|
const acc = variantAccent(variant, isDark);
|
|
4548
4689
|
const variantLabel = variant === "question" ? "Question" : variant === "journey" ? "Step" : "Node";
|
|
4549
4690
|
const shadowClr = shadowColor(isDark);
|
|
4550
4691
|
const arrowClr = arrowColor(isDark);
|
|
4551
4692
|
const amberArrow = isDark ? ACCENT.amberDark : ACCENT.amber;
|
|
4552
|
-
return /* @__PURE__ */
|
|
4553
|
-
/* @__PURE__ */
|
|
4693
|
+
return /* @__PURE__ */ jsxs12("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, position: "relative" }, children: [
|
|
4694
|
+
/* @__PURE__ */ jsx12(ToastContainer, { toasts, onDismiss: dismissToast }),
|
|
4695
|
+
/* @__PURE__ */ jsx12("style", { children: `
|
|
4554
4696
|
.fsd-editor button:focus-visible,
|
|
4555
4697
|
.fsd-editor input:focus-visible,
|
|
4556
4698
|
.fsd-editor textarea:focus-visible,
|
|
@@ -4560,12 +4702,16 @@ function FlowchartEditor({
|
|
|
4560
4702
|
outline-offset: 2px;
|
|
4561
4703
|
border-radius: 6px;
|
|
4562
4704
|
}
|
|
4705
|
+
.fsd-editor svg [role="button"]:focus-visible {
|
|
4706
|
+
outline: 2px solid ${acc.color};
|
|
4707
|
+
outline-offset: 3px;
|
|
4708
|
+
}
|
|
4563
4709
|
.fsd-editor svg[role="application"]:focus-visible {
|
|
4564
4710
|
outline: 2px solid ${acc.color};
|
|
4565
4711
|
outline-offset: -2px;
|
|
4566
4712
|
}
|
|
4567
4713
|
` }),
|
|
4568
|
-
/* @__PURE__ */
|
|
4714
|
+
/* @__PURE__ */ jsx12(
|
|
4569
4715
|
"div",
|
|
4570
4716
|
{
|
|
4571
4717
|
role: "status",
|
|
@@ -4575,28 +4721,28 @@ function FlowchartEditor({
|
|
|
4575
4721
|
children: announcement
|
|
4576
4722
|
}
|
|
4577
4723
|
),
|
|
4578
|
-
/* @__PURE__ */
|
|
4579
|
-
/* @__PURE__ */
|
|
4580
|
-
/* @__PURE__ */
|
|
4724
|
+
/* @__PURE__ */ jsx12(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
|
|
4725
|
+
/* @__PURE__ */ jsxs12("div", { style: { display: "flex", gap: 6, padding: "7px 14px", background: t.ctrlsBg, borderBottom: `1px solid ${t.ctrlsBorder}`, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
4726
|
+
/* @__PURE__ */ jsxs12("button", { onClick: () => addNode(), style: ctrlBtn(acc.color, isDark), children: [
|
|
4581
4727
|
"+ ",
|
|
4582
4728
|
variantLabel
|
|
4583
4729
|
] }),
|
|
4584
|
-
selectedSet.size > 0 && /* @__PURE__ */
|
|
4585
|
-
/* @__PURE__ */
|
|
4586
|
-
/* @__PURE__ */
|
|
4730
|
+
selectedSet.size > 0 && /* @__PURE__ */ jsxs12(Fragment5, { children: [
|
|
4731
|
+
/* @__PURE__ */ jsx12("div", { style: { width: 1, height: 20, background: t.ctrlsBorder, margin: "0 2px" } }),
|
|
4732
|
+
/* @__PURE__ */ jsx12("button", { onClick: deleteSelected, style: { ...ctrlBtn("transparent", isDark), color: "#ef4444", border: `1px solid ${isDark ? "#7f1d1d" : "#fca5a5"}` }, children: selectedSet.size > 1 ? `Delete (${selectedSet.size})` : "Delete" })
|
|
4587
4733
|
] }),
|
|
4588
|
-
liveEdge && /* @__PURE__ */
|
|
4734
|
+
liveEdge && /* @__PURE__ */ jsxs12("span", { style: { fontSize: 11, color: acc.color, fontWeight: 600, marginLeft: 6 }, children: [
|
|
4589
4735
|
liveEdge.answerLabel ? `Routing "${liveEdge.answerLabel}" \u2192` : "Drop on a node to connect",
|
|
4590
|
-
/* @__PURE__ */
|
|
4736
|
+
/* @__PURE__ */ jsx12("span", { style: { fontWeight: 400, color: t.textMuted, marginLeft: 6 }, children: "release to cancel" })
|
|
4591
4737
|
] }),
|
|
4592
|
-
/* @__PURE__ */
|
|
4738
|
+
/* @__PURE__ */ jsxs12("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
4593
4739
|
variant === "question" ? "drag answer port to connect \xB7 " : "drag port dot \xB7 ",
|
|
4594
4740
|
"scroll to zoom \xB7 drag to pan"
|
|
4595
4741
|
] })
|
|
4596
4742
|
] }),
|
|
4597
|
-
variant !== "flowchart" && /* @__PURE__ */
|
|
4598
|
-
/* @__PURE__ */
|
|
4599
|
-
/* @__PURE__ */
|
|
4743
|
+
variant !== "flowchart" && /* @__PURE__ */ jsx12("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" }),
|
|
4744
|
+
/* @__PURE__ */ jsxs12("div", { style: STYLE_FLEX_ROW, children: [
|
|
4745
|
+
/* @__PURE__ */ jsx12(
|
|
4600
4746
|
NodeNavigator,
|
|
4601
4747
|
{
|
|
4602
4748
|
model,
|
|
@@ -4610,7 +4756,7 @@ function FlowchartEditor({
|
|
|
4610
4756
|
onSelect: jumpToNode
|
|
4611
4757
|
}
|
|
4612
4758
|
),
|
|
4613
|
-
/* @__PURE__ */
|
|
4759
|
+
/* @__PURE__ */ jsx12(
|
|
4614
4760
|
DiagramCanvas,
|
|
4615
4761
|
{
|
|
4616
4762
|
model,
|
|
@@ -4745,27 +4891,27 @@ function FlowchartEditor({
|
|
|
4745
4891
|
}
|
|
4746
4892
|
}
|
|
4747
4893
|
),
|
|
4748
|
-
selected && /* @__PURE__ */
|
|
4894
|
+
selected && /* @__PURE__ */ jsx12(StepEditor, { nodeId: selected, model, onModelChange: (m) => {
|
|
4749
4895
|
applyAndPush(m);
|
|
4750
4896
|
}, variant, isDark, t, acc }, selected)
|
|
4751
4897
|
] }),
|
|
4752
|
-
/* @__PURE__ */
|
|
4753
|
-
/* @__PURE__ */
|
|
4898
|
+
/* @__PURE__ */ jsxs12("div", { style: { padding: "4px 14px", fontSize: 11, color: t.textMuted, background: t.statusBg, borderTop: `1px solid ${t.ctrlsBorder}`, display: "flex", gap: 16, flexWrap: "wrap", overflow: "hidden", maxHeight: 28 }, children: [
|
|
4899
|
+
/* @__PURE__ */ jsxs12("span", { children: [
|
|
4754
4900
|
model.nodes.length,
|
|
4755
4901
|
" ",
|
|
4756
4902
|
variantLabel.toLowerCase(),
|
|
4757
4903
|
"s"
|
|
4758
4904
|
] }),
|
|
4759
|
-
/* @__PURE__ */
|
|
4905
|
+
/* @__PURE__ */ jsxs12("span", { children: [
|
|
4760
4906
|
model.edges.length,
|
|
4761
4907
|
" connections"
|
|
4762
4908
|
] }),
|
|
4763
|
-
/* @__PURE__ */
|
|
4909
|
+
/* @__PURE__ */ jsxs12("span", { children: [
|
|
4764
4910
|
Math.round(transform.scale * 100),
|
|
4765
4911
|
"% zoom"
|
|
4766
4912
|
] }),
|
|
4767
|
-
/* @__PURE__ */
|
|
4768
|
-
selected && /* @__PURE__ */
|
|
4913
|
+
/* @__PURE__ */ jsx12("span", { style: { marginLeft: "auto", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: "Ctrl+Z undo \xB7 Ctrl+Y redo \xB7 Ctrl+0 fit \xB7 Alt+Arrow traverse" }),
|
|
4914
|
+
selected && /* @__PURE__ */ jsx12("span", { style: { color: acc.color }, children: model.nodes.find((n) => n.id === selected)?.label })
|
|
4769
4915
|
] })
|
|
4770
4916
|
] });
|
|
4771
4917
|
}
|