Keep the dragged edge on screen while the palette picks its node
Releasing an edge over empty space opened the node palette but dropped the edge
drawing, so the connection being made was invisible exactly while its target was
being chosen. The finished drag is now held in `pendingDrop` and drawn like a
running one until the palette closes, by pick or by Escape.
The e2e check for the Escape path exposed a real defect underneath: `Popup.open()`
blurs the focused widget, `close()` did not. A field inside the popup therefore kept
the keyboard bridge's DOM focus after the popup was gone, and every later Delete or
Ctrl+Z went into that invisible input. The suite caught it one check later ("Delete
removes the node and its edge" found the node still there). `close()` now blurs,
mirroring `open()`.
Verified remotely on the Windows host: 287 editor checks and 24 jump-and-run smoke
checks, no failures.
This commit is contained in:
1 parent
ebad3fd530
commit
14562cf157
5 files changed
+65
-11
No files matched your search
@@ -64,8 +64,13 @@ export class GraphCanvas extends Widget {
|
||||
moved: boolean;
|
||||
} | null = null;
|
||||
private connecting: { from: PortSide; pointer: { x: number; y: number } } | null = null;
|
||||
/** A finished drag whose palette is still open – drawn like the running one, so the connection
|
||||
* being made stays visible while the node type is chosen. */
|
||||
private pendingDrop: { from: PortSide; pointer: { x: number; y: number } } | null = null;
|
||||
/** Where the running drag would land, recomputed on every move so the drop holds no surprise. */
|
||||
private dropTarget: PortSide | null = null;
|
||||
/** Whether the last redraw actually stroked the dangling edge. */
|
||||
private tempEdgeShown = false;
|
||||
/** Fit the view once the editor has a real size (it may be hidden behind the Scene tab). */
|
||||
private needsFit = true;
|
||||
/** Supplies live execution data while playing (null otherwise). */
|
||||
@@ -298,7 +303,7 @@ export class GraphCanvas extends Widget {
|
||||
if (connecting && this.assetId) {
|
||||
if (target) this.connect(connecting.from, target);
|
||||
else if (!this.nodeUnder(e.global.x, e.global.y))
|
||||
this.openPaletteForDrop(connecting.from, e.global.x, e.global.y);
|
||||
this.openPaletteForDrop(connecting, e.global.x, e.global.y);
|
||||
}
|
||||
this.redraw();
|
||||
}
|
||||
@@ -319,16 +324,27 @@ export class GraphCanvas extends Widget {
|
||||
* Dropped into empty space: offer the node types this edge could reach, then wire the new node
|
||||
* up the same way a drop on its body would.
|
||||
*/
|
||||
private openPaletteForDrop(from: PortSide, globalX: number, globalY: number): void {
|
||||
private openPaletteForDrop(
|
||||
drag: { from: PortSide; pointer: { x: number; y: number } },
|
||||
globalX: number,
|
||||
globalY: number,
|
||||
): void {
|
||||
const graph = this.asset()?.graph;
|
||||
const from = drag.from;
|
||||
const fromType = graph && portTypeOf(graph, from);
|
||||
if (!graph || fromType === undefined) return;
|
||||
const local = this.toLocal({ x: globalX, y: globalY });
|
||||
const at = this.screenToGraph(local.x, local.y);
|
||||
new NodePalette(
|
||||
this.pendingDrop = drag;
|
||||
const palette = new NodePalette(
|
||||
(def: NodeDef) => this.addConnectedNode(def, from, fromType, at),
|
||||
(def) => defAcceptsFrom(def, fromType, from.isOutput),
|
||||
).open(globalX, globalY);
|
||||
);
|
||||
palette.onClose = () => {
|
||||
this.pendingDrop = null;
|
||||
this.redraw();
|
||||
};
|
||||
palette.open(globalX, globalY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -486,16 +502,19 @@ export class GraphCanvas extends Widget {
|
||||
for (const edge of asset.graph.edges) this.drawEdge(edge, isHot(trace?.edges, edge.id));
|
||||
|
||||
this.tempEdge.clear();
|
||||
if (this.connecting) {
|
||||
const view = this.views.get(this.connecting.from.nodeId);
|
||||
const p = view?.portPosition(this.connecting.from.port, this.connecting.from.isOutput);
|
||||
this.tempEdgeShown = false;
|
||||
const drag = this.connecting ?? this.pendingDrop;
|
||||
if (drag) {
|
||||
const view = this.views.get(drag.from.nodeId);
|
||||
const p = view?.portPosition(drag.from.port, drag.from.isOutput);
|
||||
if (view && p) {
|
||||
const a = { x: view.x + p.x, y: view.y + p.y };
|
||||
// Snapping to the resolved port makes the prediction the ring shows unmistakable.
|
||||
const b = this.anchorPoint(target) ?? this.connecting.pointer;
|
||||
const [from, to] = this.connecting.from.isOutput ? [a, b] : [b, a];
|
||||
const b = this.anchorPoint(target) ?? drag.pointer;
|
||||
const [from, to] = drag.from.isOutput ? [a, b] : [b, a];
|
||||
bezier(this.tempEdge, from.x, from.y, to.x, to.y);
|
||||
this.tempEdge.stroke({ width: 2, color: c.textMuted, alpha: 0.9 });
|
||||
this.tempEdgeShown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,6 +575,11 @@ export class GraphCanvas extends Widget {
|
||||
return this.dropTarget;
|
||||
}
|
||||
|
||||
/** Whether the dangling edge is on screen right now (running drag, or a drop awaiting its node). */
|
||||
danglingEdgeVisible(): boolean {
|
||||
return this.tempEdgeShown;
|
||||
}
|
||||
|
||||
/** Global screen position of a node's title bar centre. */
|
||||
nodeGlobal(nodeId: string): { x: number; y: number } | null {
|
||||
const view = this.views.get(nodeId);
|
||||
|
||||
Reference in new issue
Block a user