feat: drop cross-pane fonctionnel + drag-to-split (bord droit/bas)
CI / lint (push) Failing after 12s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 16s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
CI / lint (push) Failing after 12s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 16s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
- _setupPaneDropZone: dragover/drop sur .pane-container (fonctionne même si tab bar vide) - Drag-to-split: déposer un onglet dans les 25% droit → split right, 25% bas → split down - Indicateurs visuels: zone bleue semi-transparente au drop, tab-bar dashed outline - stopPropagation dans le drop handler des tabs pour éviter double-fire - CSS: .pane-drop-zone-indicator, .tab-bar.drop-target, position:relative sur pane-container
This commit is contained in:
@@ -390,6 +390,7 @@ function createPaneTabManager(paneId) {
|
||||
});
|
||||
el.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Prevent container handler from double-firing
|
||||
document.querySelectorAll('.tab-drop-indicator').forEach(d => d.remove());
|
||||
try {
|
||||
const raw = e.dataTransfer.getData('application/obsigate-tab');
|
||||
@@ -720,15 +721,99 @@ const PaneManager = {
|
||||
}
|
||||
|
||||
container.addEventListener('click', (e) => {
|
||||
// Don't capture clicks on tab items (they have their own handler)
|
||||
if (e.target.closest('.tab-item')) return;
|
||||
this.setActivePane(idx);
|
||||
});
|
||||
|
||||
// --- Drop zone handlers on the pane container ---
|
||||
this._setupPaneDropZone(container, idx);
|
||||
|
||||
grid.appendChild(container);
|
||||
return { id: idx, element: container, width: null };
|
||||
},
|
||||
|
||||
/** Add dragover/drop on a pane container for cross-pane transfers + drag-to-split */
|
||||
_setupPaneDropZone(container, paneId) {
|
||||
let _edgeZone = null; // 'right' | 'bottom' | null
|
||||
|
||||
container.addEventListener('dragover', (e) => {
|
||||
const raw = e.dataTransfer.types.includes('application/obsigate-tab');
|
||||
if (!raw) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
|
||||
// Detect edge zones for drag-to-split (only in single-pane mode)
|
||||
const rect = container.getBoundingClientRect();
|
||||
const relX = e.clientX - rect.left;
|
||||
const relY = e.clientY - rect.top;
|
||||
const isSingle = !this.isSplit();
|
||||
|
||||
// Clean up previous indicators
|
||||
container.querySelectorAll('.pane-drop-zone-indicator').forEach(d => d.remove());
|
||||
_edgeZone = null;
|
||||
|
||||
if (isSingle) {
|
||||
if (relX > rect.width * 0.75) {
|
||||
_edgeZone = 'right';
|
||||
} else if (relY > rect.height * 0.75) {
|
||||
_edgeZone = 'bottom';
|
||||
}
|
||||
}
|
||||
|
||||
if (_edgeZone) {
|
||||
// Show edge zone indicator
|
||||
const indicator = document.createElement('div');
|
||||
indicator.className = 'pane-drop-zone-indicator pane-drop-zone-' + _edgeZone;
|
||||
container.appendChild(indicator);
|
||||
} else {
|
||||
// Show tab-bar highlight for cross-pane transfer
|
||||
const tabBar = container.querySelector('.tab-bar');
|
||||
if (tabBar) tabBar.classList.add('drop-target');
|
||||
}
|
||||
});
|
||||
|
||||
container.addEventListener('dragleave', (e) => {
|
||||
container.querySelectorAll('.pane-drop-zone-indicator').forEach(d => d.remove());
|
||||
const tabBar = container.querySelector('.tab-bar');
|
||||
if (tabBar) tabBar.classList.remove('drop-target');
|
||||
_edgeZone = null;
|
||||
});
|
||||
|
||||
container.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
container.querySelectorAll('.pane-drop-zone-indicator').forEach(d => d.remove());
|
||||
const tabBar = container.querySelector('.tab-bar');
|
||||
if (tabBar) tabBar.classList.remove('drop-target');
|
||||
|
||||
const raw = e.dataTransfer.getData('application/obsigate-tab');
|
||||
if (!raw) return;
|
||||
try {
|
||||
const dragData = JSON.parse(raw);
|
||||
|
||||
if (_edgeZone === 'right') {
|
||||
// Drag to right edge → split right with this tab
|
||||
this.splitRight();
|
||||
// Transfer the dragged tab to the new pane
|
||||
setTimeout(() => {
|
||||
const newPaneId = this.panes.length - 1;
|
||||
this.transferTab(dragData.paneId, dragData.tabId, newPaneId);
|
||||
}, 100);
|
||||
} else if (_edgeZone === 'bottom') {
|
||||
// Drag to bottom edge → split down with this tab
|
||||
this.splitDown();
|
||||
setTimeout(() => {
|
||||
const newPaneId = this.panes.length - 1;
|
||||
this.transferTab(dragData.paneId, dragData.tabId, newPaneId);
|
||||
}, 100);
|
||||
} else if (dragData.paneId !== paneId) {
|
||||
// Cross-pane transfer (dropped on tab bar or content area of another pane)
|
||||
this.transferTab(dragData.paneId, dragData.tabId, paneId);
|
||||
}
|
||||
} catch (e2) { /* ignore */ }
|
||||
_edgeZone = null;
|
||||
});
|
||||
},
|
||||
|
||||
_addPane() {
|
||||
const grid = document.querySelector('.pane-grid');
|
||||
if (!grid) return;
|
||||
|
||||
@@ -1264,6 +1264,7 @@ select {
|
||||
min-height: 0;
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 150ms ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pane-container.pane-active {
|
||||
@@ -1317,6 +1318,38 @@ select {
|
||||
border: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
/* --- Drop zone indicators for drag-to-split --- */
|
||||
.pane-drop-zone-indicator {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
background: var(--accent);
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
transition: opacity 100ms ease;
|
||||
}
|
||||
|
||||
.pane-drop-zone-indicator.pane-drop-zone-right {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 25%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pane-drop-zone-indicator.pane-drop-zone-bottom {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
/* Tab bar drop target highlight */
|
||||
.tab-bar.drop-target {
|
||||
background: var(--accent);
|
||||
opacity: 0.15;
|
||||
outline: 2px dashed var(--accent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* Responsive: disable split view below 768px */
|
||||
@media (max-width: 768px) {
|
||||
.pane-grid {
|
||||
|
||||
Reference in New Issue
Block a user