Drag a pane's toolbar onto another pane to swap them
New interaction: click-and-drag any pane's toolbar onto another pane to swap their positions in the tree. The shells / scrollback stay intact (each leaf keeps its data; only the tree slot it occupies changes). Implementation: - tree.ts: `swapLeaves(root, idA, idB)` walks the tree once, substituting one leaf for the other at each occurrence. The leaf objects themselves carry their id/distro/cwd/label/broadcast across, so React preserves the LeafPane instances via the flat-list keying. - orchestration.tsx: add drag lifecycle to the context — dragSourceId / dragOverId (reactive) plus beginHeaderDrag, setHeaderDragOver, endHeaderDrag (stable methods). - App.tsx: implement those methods. endHeaderDrag(true) swaps if source and over are different leaves. - LeafPane.tsx: pointerdown on .pane-toolbar (skipped if the target is a button/input). 5px movement threshold before drag commits to prevent accidental swaps when clicking a chip etc. Pointer-capture the toolbar so we keep getting move events even outside it. Use document.elementFromPoint to find the leaf under the cursor. - CSS: source pane fades to 40% opacity during drag; target pane shows a 3px dashed blue outline; toolbar shows grab/grabbing cursors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c4747546e0
commit
c93ebddfa5
5 changed files with 172 additions and 2 deletions
37
src/App.tsx
37
src/App.tsx
|
|
@ -25,6 +25,7 @@ import {
|
|||
reshapeToPreset,
|
||||
flattenLayout,
|
||||
updateSplitRatio,
|
||||
swapLeaves,
|
||||
serialize,
|
||||
deserialize,
|
||||
presetSingle,
|
||||
|
|
@ -238,6 +239,32 @@ export default function App() {
|
|||
setNotifications((ns) => ns.filter((n) => n.id !== id));
|
||||
}, []);
|
||||
|
||||
// ---- header-drag swap ---------------------------------------------------
|
||||
const [dragSourceId, setDragSourceId] = useState<NodeId | null>(null);
|
||||
const [dragOverId, setDragOverId] = useState<NodeId | null>(null);
|
||||
const beginHeaderDrag = useCallback((leafId: NodeId) => {
|
||||
setDragSourceId(leafId);
|
||||
setDragOverId(null);
|
||||
}, []);
|
||||
const setHeaderDragOver = useCallback((leafId: NodeId | null) => {
|
||||
setDragOverId(leafId);
|
||||
}, []);
|
||||
const endHeaderDrag = useCallback(
|
||||
(commitSwap: boolean) => {
|
||||
if (
|
||||
commitSwap &&
|
||||
dragSourceId &&
|
||||
dragOverId &&
|
||||
dragSourceId !== dragOverId
|
||||
) {
|
||||
setTree((t) => swapLeaves(t, dragSourceId, dragOverId));
|
||||
}
|
||||
setDragSourceId(null);
|
||||
setDragOverId(null);
|
||||
},
|
||||
[dragSourceId, dragOverId],
|
||||
);
|
||||
|
||||
const orch = useMemo<Orchestration>(
|
||||
() => ({
|
||||
activeLeafId,
|
||||
|
|
@ -251,6 +278,11 @@ export default function App() {
|
|||
registerPaneId,
|
||||
broadcastFrom,
|
||||
notify,
|
||||
dragSourceId,
|
||||
dragOverId,
|
||||
beginHeaderDrag,
|
||||
setHeaderDragOver,
|
||||
endHeaderDrag,
|
||||
}),
|
||||
[
|
||||
activeLeafId,
|
||||
|
|
@ -264,6 +296,11 @@ export default function App() {
|
|||
registerPaneId,
|
||||
broadcastFrom,
|
||||
notify,
|
||||
dragSourceId,
|
||||
dragOverId,
|
||||
beginHeaderDrag,
|
||||
setHeaderDragOver,
|
||||
endHeaderDrag,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue