Skip to content

Commit d737ce9

Browse files
frouenemcharfadi
authored andcommitted
[6032] Ensure edge handles on border nodes are placed opposite their anchor point
Bug: #6032 Signed-off-by: Florian ROUËNÉ <florian.rouene@obeosoft.com>
1 parent 453eedb commit d737ce9

File tree

5 files changed

+106
-31
lines changed

5 files changed

+106
-31
lines changed

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ Downstream applications can now implement `IProxyRemovalServiceDelegate` to cust
250250
The node action icons are now a little smaller and only appear after the node has been hovered for 0.5s.
251251
- https://github.com/eclipse-sirius/sirius-web/issues/6022[#6022] [sirius-web] Rename the "Representations" workbench view to "Related Views"
252252
- https://github.com/eclipse-sirius/sirius-web/issues/6041[#6041] [sirius-web] Provide access to the `editingContextId`, for migration participants during the loading of the resources, thanks to an adapter on the resource set named `EditingContextAdapter`.
253+
- https://github.com/eclipse-sirius/sirius-web/issues/6032[#6032] [diagram] Ensure edge handles on border nodes are placed opposite their anchor point
253254

254255

255256

integration-tests-playwright/playwright/e2e/diagrams/bordernode.spec.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2025 Obeo.
2+
* Copyright (c) 2025, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -98,3 +98,26 @@ test.describe('diagram - borderNode', () => {
9898
expect(reactFlowXYPositionEastAfterTopLeft.x).toBe(parentSizeAfterTopLeft.width - borderNodeGap);
9999
});
100100
});
101+
test.describe('diagram - borderNode', () => {
102+
let projectId;
103+
test.beforeEach(async ({ page, request }) => {
104+
await new PlaywrightProject(request).uploadProject(page, 'projectBorderNodeHandlePosition.zip');
105+
const playwrightExplorer = new PlaywrightExplorer(page);
106+
await playwrightExplorer.expand('handlePosition');
107+
await playwrightExplorer.expand('Root');
108+
await playwrightExplorer.select('diagram');
109+
const url = page.url();
110+
const parts = url.split('/');
111+
const projectsIndex = parts.indexOf('projects');
112+
projectId = parts[projectsIndex + 1];
113+
});
114+
115+
test.afterEach(async ({ request }) => {
116+
await new PlaywrightProject(request).deleteProject(projectId);
117+
});
118+
119+
test('When an edge is on a border node, then its handle is on the opposite', async ({ page }) => {
120+
await expect(page.locator('.target_handle_right')).toHaveCount(1);
121+
await expect(page.locator('.source_handle_top')).toHaveCount(1);
122+
});
123+
});
Binary file not shown.

packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutBorderNodes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,21 @@ export const convertPositionToBorderNodePosition = (position: Position): BorderN
125125
}
126126
};
127127

128+
export const convertBorderNodePositionToPosition = (borderNodePosition: BorderNodePosition | null): Position => {
129+
switch (borderNodePosition) {
130+
case BorderNodePosition.NORTH:
131+
return Position.Top;
132+
case BorderNodePosition.EAST:
133+
return Position.Right;
134+
case BorderNodePosition.SOUTH:
135+
return Position.Bottom;
136+
case BorderNodePosition.WEST:
137+
return Position.Left;
138+
default:
139+
return Position.Right;
140+
}
141+
};
142+
128143
export const getBorderNodeParentIfExist = (
129144
node: InternalNode<Node<NodeData>>,
130145
nodeLookup: NodeLookup<InternalNode<Node<NodeData>>>

packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutHandles.ts

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2025 Obeo.
2+
* Copyright (c) 2023, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -14,7 +14,12 @@ import { InternalNode, Node, Position, XYPosition } from '@xyflow/react';
1414
import { NodeLookup } from '@xyflow/system';
1515
import { GQLDiagramDescription } from '../../representation/DiagramRepresentation.types';
1616
import { NodeData } from '../DiagramRenderer.types';
17-
import { getEdgeParameters, getNodeCenter, getUpdatedConnectionHandles } from '../edge/EdgeLayout';
17+
import {
18+
getEdgeParameters,
19+
getNodeCenter,
20+
getUpdatedConnectionHandle,
21+
getUpdatedConnectionHandles,
22+
} from '../edge/EdgeLayout';
1823
import { ConnectionHandle } from '../handles/ConnectionHandles.types';
1924
import {
2025
GetUpdatedConnectionHandlesIndexByPosition,
@@ -23,9 +28,10 @@ import {
2328
import { evaluateAbsolutePosition } from '../node/NodeUtils';
2429
import { RawDiagram } from './layout.types';
2530
import {
26-
getBorderNodeParentIfExist,
27-
convertPositionToBorderNodePosition,
2831
computeBorderNodeXYPositionFromBorderNodePosition,
32+
convertPositionToBorderNodePosition,
33+
convertBorderNodePositionToPosition,
34+
getBorderNodeParentIfExist,
2935
} from './layoutBorderNodes';
3036

3137
const getHandlesIdsFromNode = (node: Node<NodeData>): string[] => {
@@ -197,38 +203,68 @@ const layoutHandlePosition = (
197203
(updatedNodes: Node<NodeData>[], node: Node<NodeData>, _index, nodeArray) => {
198204
if (edge.source && edge.target) {
199205
if (edge.source === node.id) {
200-
if (node.data.isBorderNode && !node.data.movedByUser) {
201-
const newBorderNodePosition = convertPositionToBorderNodePosition(sourcePosition);
202-
const newXYPosition = computeBorderNodeXYPositionFromBorderNodePosition(
203-
node,
204-
nodeArray,
205-
updatedNodes,
206-
newBorderNodePosition,
207-
nodeLookup
208-
);
209-
if (newXYPosition) {
210-
node.position = newXYPosition;
206+
if (node.data.isBorderNode) {
207+
if (!node.data.movedByUser) {
208+
const newBorderNodePosition = convertPositionToBorderNodePosition(sourcePosition);
209+
const newXYPosition = computeBorderNodeXYPositionFromBorderNodePosition(
210+
node,
211+
nodeArray,
212+
updatedNodes,
213+
newBorderNodePosition,
214+
nodeLookup
215+
);
216+
if (newXYPosition) {
217+
node.position = newXYPosition;
218+
}
219+
node.data = {
220+
...node.data,
221+
borderNodePosition: newBorderNodePosition,
222+
connectionHandles: sourceConnectionHandles,
223+
};
224+
} else {
225+
const borderNodeConnectionHandles = getUpdatedConnectionHandle(
226+
sourceNode,
227+
convertBorderNodePositionToPosition(node.data.borderNodePosition),
228+
sourceHandle,
229+
'source'
230+
);
231+
node.data = { ...node.data, connectionHandles: borderNodeConnectionHandles };
211232
}
212-
node.data = { ...node.data, borderNodePosition: newBorderNodePosition };
233+
} else {
234+
node.data = { ...node.data, connectionHandles: sourceConnectionHandles };
213235
}
214-
node.data = { ...node.data, connectionHandles: sourceConnectionHandles };
215236
}
216237
if (edge.target === node.id) {
217-
if (node.data.isBorderNode && !node.data.movedByUser) {
218-
const newBorderNodePosition = convertPositionToBorderNodePosition(targetPosition);
219-
const newXYPosition = computeBorderNodeXYPositionFromBorderNodePosition(
220-
node,
221-
nodeArray,
222-
updatedNodes,
223-
newBorderNodePosition,
224-
nodeLookup
225-
);
226-
if (newXYPosition) {
227-
node.position = newXYPosition;
238+
if (node.data.isBorderNode) {
239+
if (!node.data.movedByUser) {
240+
const newBorderNodePosition = convertPositionToBorderNodePosition(targetPosition);
241+
const newXYPosition = computeBorderNodeXYPositionFromBorderNodePosition(
242+
node,
243+
nodeArray,
244+
updatedNodes,
245+
newBorderNodePosition,
246+
nodeLookup
247+
);
248+
if (newXYPosition) {
249+
node.position = newXYPosition;
250+
}
251+
node.data = {
252+
...node.data,
253+
borderNodePosition: newBorderNodePosition,
254+
connectionHandles: targetConnectionHandles,
255+
};
256+
} else {
257+
const borderNodeConnectionHandles = getUpdatedConnectionHandle(
258+
targetNode,
259+
convertBorderNodePositionToPosition(node.data.borderNodePosition),
260+
targetHandle,
261+
'target'
262+
);
263+
node.data = { ...node.data, connectionHandles: borderNodeConnectionHandles };
228264
}
229-
node.data = { ...node.data, borderNodePosition: convertPositionToBorderNodePosition(targetPosition) };
265+
} else {
266+
node.data = { ...node.data, connectionHandles: targetConnectionHandles };
230267
}
231-
node.data = { ...node.data, connectionHandles: targetConnectionHandles };
232268
}
233269
}
234270
return [...updatedNodes, node];

0 commit comments

Comments
 (0)