feat(editor): edge selection + properties-panel edge-info + canvas-wide deselect

Three flow-editor UX upgrades:

1. Wider deselect zone. A new translucent
   Positioned.fill GestureDetector wraps the outermost
   Stack (above the InteractiveViewer). Tapping the dark
   area outside the canvas grid — which used to do
   nothing — now closes the properties panel.

2. Edge selection. Click a wire → controller.selectEdge
   stamps the edge key. Click again or click background →
   deselects. Selection is mutually exclusive with step
   selection, enforced inside the controller so the panel
   never tries to render both. Selected edges highlight
   with the same accent the hover state uses.

3. Properties panel grows an edge-info mode. When an
   edge is selected, the panel shows:
   - source qualified name (e.g. inputs.document or
     summarize.response)
   - target qualified name
   - type colour swatch + name per endpoint (LabVIEW
     palette)
   - type-compatibility pill (green / red)
   - Disconnect button that removes the edge in one click
   l10n: 8 new edgeInfo* strings in EN + DE.

Editor tests still pass (20).

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-02 15:22:46 +02:00
parent 1e9968496e
commit 59aa8fe78e
5 changed files with 369 additions and 2 deletions

View file

@ -429,6 +429,26 @@ class _FlowCanvasState extends State<FlowCanvas>
: BoxDecoration(color: theme.colorScheme.surface),
child: Stack(
children: [
// Outer-most tap layer catches clicks on the
// dark area OUTSIDE the canvas grid (when the
// operator has panned / zoomed so the grid
// doesn't fill the viewport). Translucent so
// InteractiveViewer below still handles pan +
// zoom; the gesture arena gives the tap to
// whichever child claims it first, and on
// canvas-empty pointers nobody else does.
Positioned.fill(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
// Deselect both step + edge so the
// properties panel closes when the operator
// clicks the empty backdrop.
widget.controller.selectStep(null);
widget.controller.selectEdge(null);
},
),
),
InteractiveViewer(
transformationController: _transform,
constrained: false,
@ -489,7 +509,18 @@ class _FlowCanvasState extends State<FlowCanvas>
layout,
);
if (edge == null) {
// Empty canvas tap close
// properties panel (both step + edge
// selections clear).
widget.controller.selectStep(null);
widget.controller.selectEdge(null);
} else {
// Tap on a wire select that edge so
// the properties panel shows its
// source/target + type compatibility.
// Mutually exclusive with step
// selection inside the controller.
widget.controller.selectEdge(edge);
}
},
onSecondaryTapDown: (details) {
@ -1066,6 +1097,7 @@ class _FlowCanvasState extends State<FlowCanvas>
final edgeKey = '${edge.toId}:${edge.toField}';
final highlight =
_hoveredEdge == edgeKey ||
widget.controller.selectedEdgeKey == edgeKey ||
edge.fromId == widget.controller.selectedStepId ||
edge.toId == widget.controller.selectedStepId;
// Type-aware wire colours at both endpoints. Inputs-