fix(ui): +Dev inline-namer kept getting blurred by default mousedown
Root cause traced by sherlock with Playwright (docs/sherlock-+dev-bug.md
on the sherlock branch). The previous routing fix at 94869f3 was
necessary but not sufficient: placeDeviceAt() now reaches promptInline()
correctly, but the synchronous input.focus() is undone ~6ms later by
the browser's compatibility-mousedown default — which blurs the active
element when the mousedown landed on a non-focusable target (SVG rect /
SVG root). The blur listener then ran done(null) and ripped the
<foreignObject> out before m could type a name.
Primary fix: e.preventDefault() at the top of both armed-tool branches
in onCanvasPointerDown. Suppresses the focus-shifting default so the
input keeps focus.
+Frame is also wrapped for symmetry. It wasn't strictly affected (its
namer runs from pointerup, not pointerdown) but preventDefault avoids
a subtle text-selection side effect during rubber-band drag.
Secondary fix in promptInline.done(): clear activeNamer *before*
fo.remove(). Enter-key triggers a synchronous blur listener which
re-enters done() — if remove() ran first, the re-entry hit a
"node no longer a child" pageerror. Reordering makes the re-entry a
no-op (activeNamer is already null).
Verified locally: served /main.js shows e.preventDefault() inside both
tool branches and the reordered done() body. go test -race ./... still
green.
This commit is contained in:
@@ -465,11 +465,21 @@ function onCanvasPointerDown(e) {
|
||||
// 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.
|
||||
//
|
||||
// e.preventDefault() suppresses the compatibility mousedown's default
|
||||
// focus-shift. Without it, the freshly-focused inline-namer input gets
|
||||
// blurred ~6ms later by the browser's "focus nearest focusable ancestor
|
||||
// or blur active" behaviour (SVG rects are not focusable), and the
|
||||
// blur handler tears the namer down before m can type. Root cause +
|
||||
// verified fix from sherlock's Playwright shift; see docs/sherlock-+dev-bug.md
|
||||
// for the full trace.
|
||||
if (state.tool === "frame") {
|
||||
e.preventDefault();
|
||||
startFrameRubberBand(e, p);
|
||||
return;
|
||||
}
|
||||
if (state.tool === "device") {
|
||||
e.preventDefault();
|
||||
placeDeviceAt(p);
|
||||
return;
|
||||
}
|
||||
@@ -582,7 +592,14 @@ function promptInline(placeholder, cx, cy) {
|
||||
const input = fo.querySelector("input");
|
||||
input.focus();
|
||||
const done = (val) => {
|
||||
if (activeNamer === fo) { fo.remove(); activeNamer = null; }
|
||||
// Clear the flag *before* removing the node. Enter-key triggers a
|
||||
// synchronous blur on the input, which re-enters done() — and if
|
||||
// fo.remove() ran first, the second call hits a
|
||||
// "node no longer a child" pageerror. Reordering makes the second
|
||||
// re-entry a no-op (activeNamer is already null).
|
||||
if (activeNamer !== fo) return;
|
||||
activeNamer = null;
|
||||
fo.remove();
|
||||
resolve(val);
|
||||
};
|
||||
input.addEventListener("keydown", (e) => {
|
||||
|
||||
Reference in New Issue
Block a user