living-documentation 7.35.0 → 7.36.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.
|
@@ -44,6 +44,12 @@ function setTool(tool, shape) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
window.addEventListener('diagram:setTool', (e) => {
|
|
48
|
+
const detail = e.detail || {};
|
|
49
|
+
if (!detail.tool) return;
|
|
50
|
+
setTool(detail.tool, detail.shape);
|
|
51
|
+
});
|
|
52
|
+
|
|
47
53
|
function selectAll() {
|
|
48
54
|
if (!st.network || !st.nodes || !st.edges) return;
|
|
49
55
|
const ids = st.nodes.getIds();
|
|
@@ -34,6 +34,7 @@ let _draggingAnchorIds = new Set();
|
|
|
34
34
|
let _rehookEdgeId = null;
|
|
35
35
|
let _rehookHoveredNodeId = null;
|
|
36
36
|
let _rehookHoveredPortKey = null;
|
|
37
|
+
let _pointerDownSelection = { nodeIds: [], edgeIds: [] };
|
|
37
38
|
|
|
38
39
|
// Returns true when an edge can enter rehook mode (at least one non-anchor endpoint).
|
|
39
40
|
// Works for both port edges and native vis-network edges.
|
|
@@ -126,7 +127,11 @@ export function initNetwork(savedNodes, savedEdges, edgesStraight = false) {
|
|
|
126
127
|
callback(data);
|
|
127
128
|
markDirty();
|
|
128
129
|
_addEdgeFromPort = null;
|
|
129
|
-
setTimeout(() => {
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
if (st.currentTool === 'addEdge') {
|
|
132
|
+
window.dispatchEvent(new CustomEvent('diagram:setTool', { detail: { tool: 'select' } }));
|
|
133
|
+
}
|
|
134
|
+
}, 0);
|
|
130
135
|
},
|
|
131
136
|
},
|
|
132
137
|
};
|
|
@@ -138,6 +143,13 @@ export function initNetwork(savedNodes, savedEdges, edgesStraight = false) {
|
|
|
138
143
|
// Must be registered BEFORE any other capture-phase mousedown listeners on
|
|
139
144
|
// the container so it can stopImmediatePropagation for locked targets.
|
|
140
145
|
installUnlockHold(container);
|
|
146
|
+
container.addEventListener('mousedown', (e) => {
|
|
147
|
+
if (e.button !== 0) return;
|
|
148
|
+
_pointerDownSelection = {
|
|
149
|
+
nodeIds: [...(st.selectedNodeIds || [])],
|
|
150
|
+
edgeIds: [...(st.selectedEdgeIds || [])],
|
|
151
|
+
};
|
|
152
|
+
}, { capture: true });
|
|
141
153
|
|
|
142
154
|
// ── Z-order patch ──────────────────────────────────────────────────────────
|
|
143
155
|
// vis.js renders in 3 passes (normal → selected → hovered), which breaks
|
|
@@ -189,7 +201,7 @@ export function initNetwork(savedNodes, savedEdges, edgesStraight = false) {
|
|
|
189
201
|
} else if (fromIsAnchor && !toIsAnchor) {
|
|
190
202
|
level = toLevel;
|
|
191
203
|
} else {
|
|
192
|
-
level = Math.
|
|
204
|
+
level = Math.max(fromLevel, toLevel);
|
|
193
205
|
}
|
|
194
206
|
if (!edgesByLevel.has(level)) edgesByLevel.set(level, []);
|
|
195
207
|
edgesByLevel.get(level).push(edge);
|
|
@@ -1037,26 +1049,121 @@ function _distToSegment(px, py, ax, ay, bx, by) {
|
|
|
1037
1049
|
}
|
|
1038
1050
|
|
|
1039
1051
|
|
|
1040
|
-
|
|
1052
|
+
function nodeContainsCanvasPoint(id, canvasPos) {
|
|
1053
|
+
const n = st.nodes.get(id);
|
|
1054
|
+
const bn = st.network && st.network.body.nodes[id];
|
|
1055
|
+
if (!n || !bn || n.shapeType === 'anchor') return false;
|
|
1056
|
+
|
|
1057
|
+
const shapeType = n.shapeType || 'box';
|
|
1058
|
+
const defaults = SHAPE_DEFAULTS[shapeType] || [60, 28];
|
|
1059
|
+
const W = n.nodeWidth || defaults[0];
|
|
1060
|
+
const H = shapeType === 'circle' ? W : (n.nodeHeight || defaults[1]);
|
|
1061
|
+
|
|
1062
|
+
const dx = canvasPos.x - bn.x;
|
|
1063
|
+
const dy = canvasPos.y - bn.y;
|
|
1064
|
+
const rot = n.rotation || 0;
|
|
1065
|
+
const lx = rot ? dx * Math.cos(-rot) - dy * Math.sin(-rot) : dx;
|
|
1066
|
+
const ly = rot ? dx * Math.sin(-rot) + dy * Math.cos(-rot) : dy;
|
|
1067
|
+
|
|
1068
|
+
if (shapeType === 'circle' || shapeType === 'ellipse') {
|
|
1069
|
+
const rx = W / 2;
|
|
1070
|
+
const ry = H / 2;
|
|
1071
|
+
return rx > 0 && ry > 0 && ((lx * lx) / (rx * rx) + (ly * ly) / (ry * ry)) <= 1;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
return Math.abs(lx) <= W / 2 && Math.abs(ly) <= H / 2;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Returns the topmost (highest z-order) node containing canvasPos.
|
|
1041
1078
|
// Ignores anchor nodes and respects st.canonicalOrder.
|
|
1042
1079
|
function topmostNodeAt(canvasPos) {
|
|
1043
|
-
let
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1080
|
+
for (let i = st.canonicalOrder.length - 1; i >= 0; i--) {
|
|
1081
|
+
const id = st.canonicalOrder[i];
|
|
1082
|
+
if (nodeContainsCanvasPoint(id, canvasPos)) return id;
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function edgeDrawLevel(edgeData) {
|
|
1088
|
+
if (!edgeData) return -1;
|
|
1089
|
+
const fromLevel = st.canonicalOrder.indexOf(edgeData.from);
|
|
1090
|
+
const toLevel = st.canonicalOrder.indexOf(edgeData.to);
|
|
1091
|
+
if (fromLevel === -1 && toLevel === -1) return -1;
|
|
1092
|
+
if (fromLevel === -1) return toLevel;
|
|
1093
|
+
if (toLevel === -1) return fromLevel;
|
|
1094
|
+
|
|
1095
|
+
const fromNode = st.nodes.get(edgeData.from);
|
|
1096
|
+
const toNode = st.nodes.get(edgeData.to);
|
|
1097
|
+
const fromIsAnchor = fromNode && fromNode.shapeType === 'anchor';
|
|
1098
|
+
const toIsAnchor = toNode && toNode.shapeType === 'anchor';
|
|
1099
|
+
if (toIsAnchor && !fromIsAnchor) return fromLevel;
|
|
1100
|
+
if (fromIsAnchor && !toIsAnchor) return toLevel;
|
|
1101
|
+
return Math.max(fromLevel, toLevel);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function nearestPortEdgeAt(canvasPos, threshold = 8) {
|
|
1105
|
+
const portEdges = st.edges.get({ filter: (e) => e.fromPort || e.toPort });
|
|
1106
|
+
let nearest = null;
|
|
1107
|
+
let nearestDist = Infinity;
|
|
1108
|
+
for (const edge of portEdges) {
|
|
1109
|
+
const d = distanceToPortEdge(edge, canvasPos);
|
|
1110
|
+
if (d < nearestDist) {
|
|
1111
|
+
nearestDist = d;
|
|
1112
|
+
nearest = edge;
|
|
1057
1113
|
}
|
|
1058
1114
|
}
|
|
1059
|
-
return
|
|
1115
|
+
return nearest && nearestDist <= threshold ? nearest : null;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function selectableEdgesForNodes(nodeIds) {
|
|
1119
|
+
const selectedSet = new Set(nodeIds);
|
|
1120
|
+
return st.edges.get().filter((e) => {
|
|
1121
|
+
if (!selectedSet.has(e.from) || !selectedSet.has(e.to)) return false;
|
|
1122
|
+
const fromN = st.nodes.get(e.from);
|
|
1123
|
+
const toN = st.nodes.get(e.to);
|
|
1124
|
+
const isFreeArrow = fromN && fromN.shapeType === 'anchor' && toN && toN.shapeType === 'anchor';
|
|
1125
|
+
return isFreeArrow ? !(fromN.locked && toN.locked) : !e.edgeLocked;
|
|
1126
|
+
}).map((e) => e.id);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function selectNodesFromClick(nodeId, srcEvent) {
|
|
1130
|
+
const additive = !!(srcEvent && (srcEvent.metaKey || srcEvent.ctrlKey));
|
|
1131
|
+
const clicked = expandSelectionToGroup([nodeId]).filter((id) => {
|
|
1132
|
+
const n = st.nodes.get(id);
|
|
1133
|
+
return n && !n.locked && n.shapeType !== 'anchor';
|
|
1134
|
+
});
|
|
1135
|
+
if (!clicked.length) return;
|
|
1136
|
+
|
|
1137
|
+
let nodeIds;
|
|
1138
|
+
if (additive) {
|
|
1139
|
+
const next = new Set(_pointerDownSelection.nodeIds || []);
|
|
1140
|
+
const allAlreadySelected = clicked.every((id) => next.has(id));
|
|
1141
|
+
clicked.forEach((id) => {
|
|
1142
|
+
if (allAlreadySelected) next.delete(id);
|
|
1143
|
+
else next.add(id);
|
|
1144
|
+
});
|
|
1145
|
+
nodeIds = Array.from(next).filter((id) => {
|
|
1146
|
+
const n = st.nodes.get(id);
|
|
1147
|
+
return n && !n.locked && n.shapeType !== 'anchor';
|
|
1148
|
+
});
|
|
1149
|
+
} else {
|
|
1150
|
+
nodeIds = clicked;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
const edgeIds = selectableEdgesForNodes(nodeIds);
|
|
1154
|
+
_addingEdgesToSelection = true;
|
|
1155
|
+
st.network.setSelection({ nodes: nodeIds, edges: edgeIds });
|
|
1156
|
+
_addingEdgesToSelection = false;
|
|
1157
|
+
st.selectedNodeIds = nodeIds;
|
|
1158
|
+
st.selectedEdgeIds = edgeIds;
|
|
1159
|
+
_rehookEdgeId = _rehookHoveredNodeId = _rehookHoveredPortKey = null;
|
|
1160
|
+
|
|
1161
|
+
hideEdgePanel();
|
|
1162
|
+
if (nodeIds.length) showNodePanel();
|
|
1163
|
+
else {
|
|
1164
|
+
hideNodePanel();
|
|
1165
|
+
hideSelectionOverlay();
|
|
1166
|
+
}
|
|
1060
1167
|
}
|
|
1061
1168
|
|
|
1062
1169
|
function onDoubleClick(params) {
|
|
@@ -1229,8 +1336,8 @@ function onClickNode(params) {
|
|
|
1229
1336
|
update.color = { color: 'rgba(0,0,0,0)', highlight: 'rgba(0,0,0,0)', hover: 'rgba(0,0,0,0)' };
|
|
1230
1337
|
update.arrows = { to: { enabled: false }, from: { enabled: false } };
|
|
1231
1338
|
}
|
|
1232
|
-
st.edges.update(update);
|
|
1233
1339
|
pushSnapshot();
|
|
1340
|
+
st.edges.update(update);
|
|
1234
1341
|
markDirty();
|
|
1235
1342
|
const edgeId = edgeData.id;
|
|
1236
1343
|
_rehookHoveredNodeId = _rehookHoveredPortKey = null;
|
|
@@ -1244,14 +1351,34 @@ function onClickNode(params) {
|
|
|
1244
1351
|
}
|
|
1245
1352
|
}
|
|
1246
1353
|
|
|
1247
|
-
// When a node is reported, params.edges is
|
|
1354
|
+
// When a node is reported, params.edges is usually empty — vis-network short-circuits
|
|
1248
1355
|
// edge detection once a node is found. Fix: call getEdgeAt() directly with CLIENT
|
|
1249
1356
|
// coordinates. params.pointer.DOM is already offset-relative to the container, so
|
|
1250
1357
|
// passing it to getEdgeAt() causes a double-subtraction of the container rect and
|
|
1251
1358
|
// returns null. Passing clientX/Y lets vis-network do its own pixel-perfect detection.
|
|
1252
1359
|
if (params.nodes.length > 0) {
|
|
1253
1360
|
const clientPos = { x: params.event.srcEvent.clientX, y: params.event.srcEvent.clientY };
|
|
1254
|
-
const
|
|
1361
|
+
const nativeEdgeId = st.network.getEdgeAt(clientPos);
|
|
1362
|
+
const portEdge = nearestPortEdgeAt(params.pointer.canvas);
|
|
1363
|
+
const edgeId = nativeEdgeId || (portEdge && portEdge.id);
|
|
1364
|
+
|
|
1365
|
+
// First honour the app-managed z-order. vis-network may report an edge or a
|
|
1366
|
+
// lower node at the click point; compare draw levels so the visibly top
|
|
1367
|
+
// element gets the click.
|
|
1368
|
+
const top = topmostNodeAt(params.pointer.canvas);
|
|
1369
|
+
if (top) {
|
|
1370
|
+
const topNode = st.nodes.get(top);
|
|
1371
|
+
const topLevel = st.canonicalOrder.indexOf(top);
|
|
1372
|
+
const edgeLevel = edgeId ? edgeDrawLevel(st.edges.get(edgeId)) : -1;
|
|
1373
|
+
if (topNode && !topNode.locked && topNode.shapeType !== 'anchor') {
|
|
1374
|
+
if (!edgeId || topLevel >= edgeLevel) {
|
|
1375
|
+
setTimeout(() => {
|
|
1376
|
+
selectNodesFromClick(top, params.event.srcEvent);
|
|
1377
|
+
}, 0);
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1255
1382
|
if (edgeId) {
|
|
1256
1383
|
const edge = st.edges.get(edgeId);
|
|
1257
1384
|
const fromN = edge && st.nodes.get(edge.from);
|
|
@@ -1277,12 +1404,12 @@ function onClickNode(params) {
|
|
|
1277
1404
|
return;
|
|
1278
1405
|
}
|
|
1279
1406
|
// No edge at click position — apply z-order correction for the topmost node.
|
|
1280
|
-
const
|
|
1407
|
+
const fallbackTop = topmostNodeAt(params.pointer.canvas);
|
|
1281
1408
|
const clickable = params.nodes.filter(id => {
|
|
1282
1409
|
const n = st.nodes.get(id);
|
|
1283
1410
|
return n && !n.locked && n.shapeType !== 'anchor';
|
|
1284
1411
|
});
|
|
1285
|
-
if (!
|
|
1412
|
+
if (!fallbackTop || !clickable.includes(fallbackTop)) {
|
|
1286
1413
|
const fallbackEdgeId = params.edges.length > 0 ? params.edges[0] : null;
|
|
1287
1414
|
if (fallbackEdgeId) {
|
|
1288
1415
|
setTimeout(() => {
|
|
@@ -1305,19 +1432,12 @@ function onClickNode(params) {
|
|
|
1305
1432
|
// Also check the canvas for port-edge proximity when nothing was hit.
|
|
1306
1433
|
if (params.nodes.length === 0 && params.edges.length === 0) {
|
|
1307
1434
|
const cp = params.pointer.canvas;
|
|
1308
|
-
const THRESHOLD = 8;
|
|
1309
1435
|
|
|
1310
1436
|
// ── Port edge proximity check ──────────────────────────────────────────
|
|
1311
1437
|
// vis-network's hit detection uses the invisible centre-to-centre ghost,
|
|
1312
1438
|
// so port edges that diverge visually from that path are not selectable.
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
let nearest = null, nearestDist = Infinity;
|
|
1316
|
-
for (const edge of portEdges) {
|
|
1317
|
-
const d = distanceToPortEdge(edge, cp);
|
|
1318
|
-
if (d < nearestDist) { nearestDist = d; nearest = edge; }
|
|
1319
|
-
}
|
|
1320
|
-
if (nearest && nearestDist <= THRESHOLD) {
|
|
1439
|
+
const nearest = nearestPortEdgeAt(cp);
|
|
1440
|
+
if (nearest) {
|
|
1321
1441
|
st.network.selectEdges([nearest.id]);
|
|
1322
1442
|
st.selectedEdgeIds = [nearest.id];
|
|
1323
1443
|
st.selectedNodeIds = [];
|