fix(dragdrop): unify tree+tab drop zone logic, fix cross-pane transfer
CI / lint (push) Failing after 9s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 18s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
CI / lint (push) Failing after 9s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 18s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
Three fixes for drag-and-drop in split view (#75): 1. Tree drag-to-split: edge zone detection now runs BEFORE the tree/tab dispatch, so dragging a sidebar file to the right/bottom edge of a single pane correctly creates a split and opens the file in the new pane (was: tree check returned early before edge zone logic). 2. Cross-pane transfer consistency: transferTab() now tracks pane count before the transfer — if closePane() fires inside transferTabTo() (source pane becomes empty), the DOM is rebuilt and the original targetTm reference is stale. After close, re-lookup the tab manager that actually holds the transferred tab before _renderTabs()+activate. 3. dragdrop.js flicker: onDragLeave() now checks relatedTarget instead of unconditionally clearing highlights on every child boundary.
This commit is contained in:
Vendored
+3
-2
@@ -196,10 +196,11 @@ function onDragOver(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onDragLeave(e) {
|
function onDragLeave(e) {
|
||||||
// Only clear when actually leaving the container, not entering a child
|
// Only clear when actually leaving the container, not entering a child.
|
||||||
|
// Use relatedTarget: if it's still inside #vault-tree, keep highlights.
|
||||||
const container = document.getElementById('vault-tree');
|
const container = document.getElementById('vault-tree');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
// Use a small delay to avoid flickering when moving between children
|
if (e.relatedTarget && container.contains(e.relatedTarget)) return;
|
||||||
clearAllHighlights();
|
clearAllHighlights();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+71
-29
@@ -659,12 +659,29 @@ const PaneManager = {
|
|||||||
const fromTm = this.tabManagers[fromPaneId];
|
const fromTm = this.tabManagers[fromPaneId];
|
||||||
const toTm = this.tabManagers[toPaneId];
|
const toTm = this.tabManagers[toPaneId];
|
||||||
if (!fromTm || !toTm) return false;
|
if (!fromTm || !toTm) return false;
|
||||||
|
|
||||||
|
// Track state BEFORE transfer (closePane may rebuild DOM)
|
||||||
|
const paneCountBefore = this.panes.length;
|
||||||
|
|
||||||
const ok = fromTm.transferTabTo(tabId, toTm);
|
const ok = fromTm.transferTabTo(tabId, toTm);
|
||||||
if (ok) {
|
if (!ok) return false;
|
||||||
toTm._renderTabs();
|
|
||||||
toTm.activate(tabId);
|
// If source pane was auto-closed, DOM was rebuilt — re-lookup target
|
||||||
this._persist();
|
let actualTm = toTm;
|
||||||
|
if (this.panes.length < paneCountBefore) {
|
||||||
|
// Find which pane manager now holds the tab
|
||||||
|
for (const [pid, tm] of Object.entries(this.tabManagers)) {
|
||||||
|
if (tm._tabs.find(t => t.id === tabId)) {
|
||||||
|
actualTm = tm;
|
||||||
|
console.log('[PaneManager] source closed, tab now in pane', pid);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actualTm._renderTabs();
|
||||||
|
actualTm.activate(tabId);
|
||||||
|
this._persist();
|
||||||
return ok;
|
return ok;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1060,15 +1077,53 @@ const PaneManager = {
|
|||||||
|
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
|
|
||||||
// Handle tree drag (file from sidebar)
|
// --- Determine drop zone ---
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const relX = e.clientX - rect.left;
|
||||||
|
const relY = e.clientY - rect.top;
|
||||||
|
const isSingle = !this.isSplit();
|
||||||
|
const onRightEdge = isSingle && relX > rect.width * 0.72;
|
||||||
|
const onBottomEdge = isSingle && relY > rect.height * 0.72;
|
||||||
|
|
||||||
|
console.log('[PaneManager] drop at', relX.toFixed(0), relY.toFixed(0),
|
||||||
|
'rect', rect.width.toFixed(0), 'x', rect.height.toFixed(0),
|
||||||
|
'isSingle', isSingle, 'paneId', paneId,
|
||||||
|
'onRightEdge', onRightEdge, 'onBottomEdge', onBottomEdge);
|
||||||
|
|
||||||
|
// --- Tree drag (file from sidebar) ---
|
||||||
if (raw.startsWith('tree:')) {
|
if (raw.startsWith('tree:')) {
|
||||||
try {
|
try {
|
||||||
const treeData = JSON.parse(raw.substring(5));
|
const treeData = JSON.parse(raw.substring(5));
|
||||||
if (treeData.vault && treeData.path) {
|
if (!treeData.vault || !treeData.path) return;
|
||||||
console.log('[PaneManager] tree drop → opening in pane', paneId);
|
console.log('[PaneManager] tree drop vault=', treeData.vault, 'path=', treeData.path);
|
||||||
const tm = window.PaneManager.getTabManager(paneId);
|
|
||||||
|
if (onRightEdge) {
|
||||||
|
console.log('[PaneManager] → tree splitRight');
|
||||||
|
this.splitRight();
|
||||||
|
setTimeout(() => {
|
||||||
|
const newPaneId = this.panes.length - 1;
|
||||||
|
const tm = this.getTabManager(newPaneId);
|
||||||
if (tm) {
|
if (tm) {
|
||||||
window.PaneManager.setActivePane(paneId);
|
this.setActivePane(newPaneId);
|
||||||
|
tm.openPreview(treeData.vault, treeData.path);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
} else if (onBottomEdge) {
|
||||||
|
console.log('[PaneManager] → tree splitDown');
|
||||||
|
this.splitDown();
|
||||||
|
setTimeout(() => {
|
||||||
|
const newPaneId = this.panes.length - 1;
|
||||||
|
const tm = this.getTabManager(newPaneId);
|
||||||
|
if (tm) {
|
||||||
|
this.setActivePane(newPaneId);
|
||||||
|
tm.openPreview(treeData.vault, treeData.path);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
} else {
|
||||||
|
console.log('[PaneManager] → tree open in pane', paneId);
|
||||||
|
const tm = this.getTabManager(paneId);
|
||||||
|
if (tm) {
|
||||||
|
this.setActivePane(paneId);
|
||||||
tm.openPreview(treeData.vault, treeData.path);
|
tm.openPreview(treeData.vault, treeData.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1076,39 +1131,26 @@ const PaneManager = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tab drag
|
// --- Tab drag ---
|
||||||
let dragData;
|
let dragData;
|
||||||
try { dragData = JSON.parse(raw); } catch (e2) { console.log('[PaneManager] JSON parse error:', e2); return; }
|
try { dragData = JSON.parse(raw); } catch (e2) { console.log('[PaneManager] JSON parse error:', e2); return; }
|
||||||
if (dragData.paneId == null || dragData.tabId == null) { console.log('[PaneManager] missing paneId/tabId'); return; }
|
if (dragData.paneId == null || dragData.tabId == null) { console.log('[PaneManager] missing paneId/tabId'); return; }
|
||||||
|
|
||||||
// Recalculate edge zone from drop coordinates
|
if (onRightEdge) {
|
||||||
const rect = container.getBoundingClientRect();
|
|
||||||
const relX = e.clientX - rect.left;
|
|
||||||
const relY = e.clientY - rect.top;
|
|
||||||
const isSingle = !this.isSplit();
|
|
||||||
|
|
||||||
console.log('[PaneManager] drop at', relX.toFixed(0), relY.toFixed(0),
|
|
||||||
'rect', rect.width.toFixed(0), 'x', rect.height.toFixed(0),
|
|
||||||
'isSingle', isSingle, 'paneId', paneId, 'srcPane', dragData.paneId);
|
|
||||||
|
|
||||||
if (isSingle && relX > rect.width * 0.72) {
|
|
||||||
console.log('[PaneManager] → splitRight');
|
console.log('[PaneManager] → splitRight');
|
||||||
this.splitRight();
|
this.splitRight();
|
||||||
// Transfer after DOM settles
|
setTimeout(() => {
|
||||||
const transfer = () => {
|
|
||||||
const newPaneId = this.panes.length - 1;
|
const newPaneId = this.panes.length - 1;
|
||||||
console.log('[PaneManager] transferring tab', dragData.tabId, 'to pane', newPaneId);
|
console.log('[PaneManager] transferring tab', dragData.tabId, 'to pane', newPaneId);
|
||||||
this.transferTab(dragData.paneId, dragData.tabId, newPaneId);
|
this.transferTab(dragData.paneId, dragData.tabId, newPaneId);
|
||||||
};
|
}, 200);
|
||||||
setTimeout(transfer, 200);
|
} else if (onBottomEdge) {
|
||||||
} else if (isSingle && relY > rect.height * 0.72) {
|
|
||||||
console.log('[PaneManager] → splitDown');
|
console.log('[PaneManager] → splitDown');
|
||||||
this.splitDown();
|
this.splitDown();
|
||||||
const transfer = () => {
|
setTimeout(() => {
|
||||||
const newPaneId = this.panes.length - 1;
|
const newPaneId = this.panes.length - 1;
|
||||||
this.transferTab(dragData.paneId, dragData.tabId, newPaneId);
|
this.transferTab(dragData.paneId, dragData.tabId, newPaneId);
|
||||||
};
|
}, 200);
|
||||||
setTimeout(transfer, 200);
|
|
||||||
} else if (dragData.paneId !== paneId) {
|
} else if (dragData.paneId !== paneId) {
|
||||||
console.log('[PaneManager] → cross-pane transfer', dragData.paneId, '→', paneId);
|
console.log('[PaneManager] → cross-pane transfer', dragData.paneId, '→', paneId);
|
||||||
this.transferTab(dragData.paneId, dragData.tabId, paneId);
|
this.transferTab(dragData.paneId, dragData.tabId, paneId);
|
||||||
|
|||||||
Reference in New Issue
Block a user