A drop asks the area the pointer already uses, so a port at the very edge is still the node
`nodeUnder()` recomputed the node's face as `0 <= x <= w`, while the pointer events follow the `hitArea`, which reaches `PORT_HIT_RADIUS` past the edge. An input port sits at exactly x = 0, so the global point rounded back landed a hair outside: the drop counted as one on empty canvas, the node palette opened and swallowed every later click – four checks of step 6 at once. `NodeView` now answers from that same `hitArea`, and the step also checks that no palette is open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8143130ed2
commit
98fa9907fd
3 files changed
+16
-3
No files matched your search
@@ -655,7 +655,7 @@ export class GraphCanvas extends Widget {
|
||||
private nodeUnder(globalX: number, globalY: number): string | null {
|
||||
for (const view of this.topDownViews()) {
|
||||
const p = view.toLocal({ x: globalX, y: globalY });
|
||||
if (p.x >= 0 && p.x <= view.w && p.y >= 0 && p.y <= view.h) return view.node.id;
|
||||
if (view.containsLocal(p.x, p.y)) return view.node.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -137,6 +137,15 @@ export class NodeView extends Widget {
|
||||
this.hitArea = new Rectangle(-PORT_HIT_RADIUS, 0, w + PORT_HIT_RADIUS * 2, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a local point is on this node, by the same area its pointer events use. An input port
|
||||
* sits at exactly x = 0, and rounding a global point back can land a hair outside it – which made
|
||||
* a drop on such a port count as a drop on empty canvas and opened the palette.
|
||||
*/
|
||||
containsLocal(x: number, y: number): boolean {
|
||||
return this.hitArea?.contains(x, y) ?? false;
|
||||
}
|
||||
|
||||
/** `project` resolves asset references (Spawn, Is asset). */
|
||||
sync(
|
||||
node: GraphNode,
|
||||
|
||||
@@ -128,9 +128,13 @@ export async function graph(t) {
|
||||
);
|
||||
const dataPort = await json(`__gm.graph.portAt(${JSON.stringify(newNode.id)}, 'message', false)`);
|
||||
await drag(fromPort.x, fromPort.y, dataPort.x, dataPort.y, 16);
|
||||
// Released on the port itself, which sits at the node's very edge: that is a drop on the node, not
|
||||
// on empty canvas. An open palette here swallows every later click – it did, for four checks.
|
||||
check(
|
||||
'incompatible ports are rejected',
|
||||
(await graphCounts()).edges === before13.edges + 1 && s.mode === 'edit',
|
||||
'incompatible ports are rejected without taking the drop for empty canvas',
|
||||
(await graphCounts()).edges === before13.edges + 1 &&
|
||||
(await state()).mode === 'edit' &&
|
||||
!(await evaluate(`__gm.locate('Search nodes…')`)),
|
||||
);
|
||||
await shot('18-connected');
|
||||
|
||||
|
||||
Reference in new issue
Block a user