Phase 3: drag pane past window edge to detach
Extends the existing header-drag gesture (which swaps panes inside the window) with an "outside the window" case: release the drag more than 60px past any viewport edge and the pane detaches into a new window via the same moveToNewWindow path the right-click menu uses. The 60px slop avoids triggering on accidental release over the OS titlebar / window chrome — without it any drag that ended above clientY=0 would fire as a detach, which is wrong because that area is still inside the user's window. No backend changes — Phase 2's transfer mechanism already handles everything; this just wires a second entry point. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8ad51787fc
commit
6faf7e5e19
3 changed files with 32 additions and 6 deletions
|
|
@ -268,6 +268,12 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
|||
[orch.beginHeaderDrag, orch.setHeaderDragOver, leaf.id],
|
||||
);
|
||||
|
||||
/** How far past a viewport edge the cursor must travel before a release
|
||||
* is treated as "drag pane out of window" instead of "drop on empty
|
||||
* space inside this window". Picked so an accidental release on the OS
|
||||
* titlebar (~30px tall) stays inside the threshold. */
|
||||
const PANE_DRAG_OUT_MARGIN = 60;
|
||||
|
||||
const onToolbarPointerUp = useCallback(
|
||||
(e: ReactPointerEvent<HTMLDivElement>) => {
|
||||
const st = dragStartRef.current;
|
||||
|
|
@ -275,12 +281,26 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
|||
(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
|
||||
const wasDragging = st.dragging;
|
||||
dragStartRef.current = null;
|
||||
if (wasDragging) {
|
||||
document.body.style.cursor = "";
|
||||
if (!wasDragging) return;
|
||||
document.body.style.cursor = "";
|
||||
|
||||
const releasedFarOutside =
|
||||
e.clientX < -PANE_DRAG_OUT_MARGIN ||
|
||||
e.clientX > window.innerWidth + PANE_DRAG_OUT_MARGIN ||
|
||||
e.clientY < -PANE_DRAG_OUT_MARGIN ||
|
||||
e.clientY > window.innerHeight + PANE_DRAG_OUT_MARGIN;
|
||||
|
||||
if (releasedFarOutside) {
|
||||
// Cancel any in-flight swap state without committing, then pop
|
||||
// this pane into a fresh window. moveToNewWindow handles the
|
||||
// PTY-handoff + closeLeaf in the source.
|
||||
orch.endHeaderDrag(false);
|
||||
orch.moveToNewWindow(leaf.id);
|
||||
} else {
|
||||
orch.endHeaderDrag(true);
|
||||
}
|
||||
},
|
||||
[orch.endHeaderDrag],
|
||||
[orch.endHeaderDrag, orch.moveToNewWindow, leaf.id],
|
||||
);
|
||||
|
||||
const onToolbarPointerCancel = useCallback(
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ export const SHORTCUT_SECTIONS: ShortcutSection[] = [
|
|||
description:
|
||||
"Pop the active pane into a fresh tiletopia window (PTY survives the move; scrollback ring replays)",
|
||||
},
|
||||
{
|
||||
keys: "Drag pane toolbar past the window edge",
|
||||
description:
|
||||
"Same as the right-click action — release the drag well outside the window to detach into a new window",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -126,8 +131,8 @@ export const TIPS: TipSpec[] = [
|
|||
body: "http and https URLs in terminal output get underlined and open in your default browser on click.",
|
||||
},
|
||||
{
|
||||
title: "Drag pane headers to swap",
|
||||
body: "Grab a pane's title bar and drag it onto another pane to swap their tree positions. Useful for reorganizing without keyboard.",
|
||||
title: "Drag pane headers to swap or detach",
|
||||
body: "Grab a pane's title bar and drag onto another pane to swap their tree positions. Drag well outside the window edge (more than ~60px past) and release to detach the pane into a new window — same mechanism as the right-click 'Move to new window' action, PTY stays alive.",
|
||||
},
|
||||
{
|
||||
title: "Workspace persistence",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue