PageScene: commit panel transform session on mouseup / Enter / Ctrl+Z

Follow-up to 72071c0066. The session-based panel transform recording
fixed pollution but introduced a new bug: while editing the same
object's panel without changing selection, the session never
committed, so Ctrl+Z fell through to the most recent prior entry —
typically the CreateAction for the shape, which deleted it on undo.

Add three document-level commit triggers covering the natural editing
boundaries the user expects:

- mouseup (any document-level mouseup): catches the end of an
  InputVector numeric-scrub drag and any click that moves focus
  elsewhere after typing.
- Enter key: explicit "done editing" commit even when the input
  retains focus.
- Ctrl+Z / Ctrl+Y (capture phase): flushes the pending session before
  the engine's keymap handler runs undo/redo, so the pending edit is
  what gets reverted instead of the prior entry.

Capture phase on the keydown listener is load-bearing: keymap.ts
attaches its handler in bubble phase via useViewportKeyboard. Capture
fires first, so the session commits before keymap dispatches.

mouseup commit is a no-op when no session is open, so attaching it
to document doesn't cost anything for unrelated clicks.

Color session is unchanged (focus/blur boundaries on the color
input still work and don't have this issue).
This commit is contained in:
Tanish Manku committed 2026-05-01 04:57:07 -07:00
1 parent 5d3cf8bc4a
commit e8db3f577a
1 file changed
+34
@@ -172,6 +172,40 @@ export const ControlPanelSceneObject = () => {
return () => commitPanelTransform();
}, []);
// Document-level commit triggers for the transform session. These
// are the natural "end of edit gesture" signals — without them, a
// pending session sits open during same-uuid editing and Ctrl+Z
// falls through to a prior entry (often the CreateAction that
// added the shape, so the shape disappears on undo).
//
// - mouseup: end of an InputVector numeric-scrub drag, OR a click
// that moves focus away after typing.
// - Enter: explicit "I'm done" without losing focus.
// - Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z (capture phase): flush before the
// engine's keymap handler runs the undo, so the pending entry is
// the one being reverted.
useEffect(() => {
const flush = () => {
transformSessionRef.current?.commit();
transformSessionRef.current = null;
};
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Enter") flush();
if (
(e.ctrlKey || e.metaKey) &&
(e.code === "KeyZ" || e.code === "KeyY")
) {
flush();
}
};
document.addEventListener("mouseup", flush);
document.addEventListener("keydown", onKeyDown, true);
return () => {
document.removeEventListener("mouseup", flush);
document.removeEventListener("keydown", onKeyDown, true);
};
}, []);
if (!currentSceneObject) {
return null;
}