fix(ui): +Dev inside a frame was silently dropped

onCanvasPointerDown returned early whenever the click landed on a
[data-device-id] or [data-frame-id] element so the per-element drag
handlers wouldn't get hijacked. Problem: this early-return fired BEFORE
the tool check, so clicking +Dev inside an existing frame never reached
placeDeviceAt().

Reordered: tool-armed branches run first and short-circuit. Only when
no tool is armed does the "click started on a child element — leave it
alone" guard kick in. End behaviour:
- +Dev anywhere (incl. inside a frame) drops a device. frame_id
  auto-resolves via the existing frameAt() point-in-rect.
- +Frm anywhere (incl. inside an existing frame) starts a rubber-band;
  rare but not harmful.
- No tool armed: clicking a device/frame still goes to its own handler
  (drag / select). Clicking empty canvas still clears selection.

Hand-tested via the served /main.js + the equivalent backend POST/PATCH
sequence: device-in-frame, device-outside, device-drag, frame-drag with
cascaded device patches — all work.
This commit is contained in:
mAi
2026-05-15 20:33:17 +02:00
parent a9e6d7aa62
commit 94869f342e

View File

@@ -458,12 +458,13 @@ let rubberStart = /** @type {{x:number,y:number}|null} */ (null);
function onCanvasPointerDown(e) { function onCanvasPointerDown(e) {
if (!state.active) return; if (!state.active) return;
// Ignore clicks that started on a device/frame — their own handlers
// captured the pointer already.
if (e.target instanceof Element && e.target.closest("[data-device-id], [data-frame-id]")) return;
const p = svgPoint(e); const p = svgPoint(e);
// Armed tool wins: a click anywhere on the canvas — including on top
// of an existing frame or device — fires the tool. The +Dev tool needs
// this so m can drop a device inside a frame; without it the frame's
// own pointerdown handler would steal the click and start a drag.
if (state.tool === "frame") { if (state.tool === "frame") {
startFrameRubberBand(e, p); startFrameRubberBand(e, p);
return; return;
@@ -472,6 +473,11 @@ function onCanvasPointerDown(e) {
placeDeviceAt(p); placeDeviceAt(p);
return; return;
} }
// No tool armed: clicks that started on a device/frame go to their
// own handlers (drag / select). Leave them alone.
if (e.target instanceof Element && e.target.closest("[data-device-id], [data-frame-id]")) return;
// Plain canvas click = clear selection. // Plain canvas click = clear selection.
if (state.selection) { state.selection = null; render(); } if (state.selection) { state.selection = null; render(); }
} }