Replace drag-promote gesture with Ctrl+Shift+P keyboard shortcut

This commit is contained in:
megaproxy 2026-05-25 20:58:43 +01:00
parent 8e4a358aa8
commit 5085326cb1
6 changed files with 142 additions and 373 deletions

View file

@ -18,7 +18,6 @@ import {
type Orientation,
type LeafNode,
type LeafShellSpec,
type Box,
newLeaf,
splitLeaf,
closeLeaf,
@ -36,7 +35,7 @@ import {
updateSplitRatio,
swapLeaves,
findNeighborInDirection,
promoteFromGutter,
promoteLeaf,
MIN_PANE_PX,
type Direction,
serialize,
@ -260,6 +259,22 @@ export default function App() {
setTree((t) => toggleBroadcastInTree(t, leafId));
}, []);
// Ctrl+Shift+P: pop the active leaf out one level. The keyboard
// replacement for the (removed) drag-past-sibling gesture. No-op with a
// toast if the leaf is at the root or its parent shares orientation
// with the grandparent — no perpendicular promotion available.
const promoteActive = useCallback(
(leafId: NodeId) => {
const next = promoteLeaf(treeRef.current, leafId);
if (next === null) {
notify("Pane can't be promoted (no perpendicular split above it)");
return;
}
setTree(next);
},
[notify],
);
const setActive = useCallback((leafId: NodeId) => {
setActiveLeafId(leafId);
}, []);
@ -415,12 +430,16 @@ export default function App() {
e.preventDefault();
e.stopPropagation();
toggleBroadcast(activeLeafId);
} else if (key === "p") {
e.preventDefault();
e.stopPropagation();
promoteActive(activeLeafId);
}
}
window.addEventListener("keydown", onKey, true);
return () => window.removeEventListener("keydown", onKey, true);
}, [split, close, toggleBroadcast]);
}, [split, close, toggleBroadcast, promoteActive]);
const registerPaneId = useCallback(
(leafId: NodeId, paneId: PaneId | null) => {
@ -586,18 +605,6 @@ export default function App() {
setTree((t) => updateSplitRatio(t, splitId, ratio));
}, []);
// ---- promote-out gesture state -----------------------------------------
// armedPromotionBox is non-null while the user is mid-drag and past the
// 75% threshold on a sibling pane. We render a translucent preview at
// that position so the user knows what releasing will do.
const [armedPromotionBox, setArmedPromotionBox] = useState<Box | null>(null);
const onGutterArmedChange = useCallback((box: Box | null) => {
setArmedPromotionBox(box);
}, []);
const onGutterPromote = useCallback((splitId: NodeId) => {
setTree((t) => promoteFromGutter(t, splitId) ?? t);
}, []);
// ---- global broadcast state (derived from tree) -------------------------
const broadcastStats = useMemo(() => {
let on = 0;
@ -742,25 +749,8 @@ export default function App() {
info={g}
containerRef={paneWrapRef}
onRatioChange={onGutterRatio}
onArmedChange={onGutterArmedChange}
onPromote={onGutterPromote}
/>
))}
{armedPromotionBox && (
<div
className="promote-preview"
style={{
position: "absolute",
top: `${armedPromotionBox.top * 100}%`,
left: `${armedPromotionBox.left * 100}%`,
width: `${armedPromotionBox.width * 100}%`,
height: `${armedPromotionBox.height * 100}%`,
pointerEvents: "none",
zIndex: 20,
}}
aria-hidden="true"
/>
)}
</OrchestrationProvider>
)}
</div>