fix: nested split pane flex squeezing and add divider drag resize

This commit is contained in:
2026-09-23 00:23:59 +08:00
parent 6d315447a1
commit 91c0c8f641
2 changed files with 88 additions and 11 deletions
+16 -7
View File
@@ -14,8 +14,11 @@
.split-container {
display: flex;
flex: 1 1 0%;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
position: relative;
}
@@ -29,14 +32,14 @@
}
.split-pane {
flex: 1;
flex: 1 1 0%;
display: flex;
position: relative;
overflow: hidden;
border: 1px solid transparent;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
min-width: 120px;
min-height: 80px;
min-width: 0;
min-height: 0;
}
.split-pane.focused {
@@ -49,6 +52,7 @@
height: 100%;
padding: 6px 10px;
overflow: hidden;
box-sizing: border-box;
}
.xterm {
@@ -65,19 +69,24 @@
.split-divider {
background: var(--border-color);
z-index: 10;
transition: background 0.2s ease;
flex-shrink: 0;
flex-grow: 0;
transition: background 0.15s ease, box-shadow 0.15s ease;
position: relative;
}
.split-container.horizontal > .split-divider {
width: 2px;
width: 4px;
cursor: col-resize;
}
.split-container.vertical > .split-divider {
height: 2px;
height: 4px;
cursor: row-resize;
}
.split-divider:hover {
.split-divider:hover,
.split-divider.dragging {
background: var(--accent-color);
box-shadow: 0 0 10px var(--accent-glow);
}
+71 -3
View File
@@ -8,7 +8,7 @@ export class TabSplitTree {
this.onEmpty = options.onEmpty || (() => {});
this.onTitleChange = options.onTitleChange || (() => {});
this.terminals = new Map(); // id -> TerminalView
this.terminals = new Map(); // id -> { termView, paneElement, parentElement }
this.activeTerminal = null;
this.rootElement = document.createElement('div');
@@ -62,10 +62,14 @@ export class TabSplitTree {
const currentPane = currentInfo.paneElement;
const parent = currentPane.parentElement;
// 创建一个新的 split-container 容器替换原有 pane,实现嵌套分屏
// 创建一个新的 split-container 容器替换原有 pane,实现嵌套等比分屏
const splitWrapper = document.createElement('div');
splitWrapper.className = `split-container ${direction}`;
// 继承原窗格在祖父容器中的 flex 权重
splitWrapper.style.flex = currentPane.style.flex || '1 1 0%';
currentPane.style.flex = '1 1 0%';
parent.replaceChild(splitWrapper, currentPane);
splitWrapper.appendChild(currentPane);
@@ -76,6 +80,11 @@ export class TabSplitTree {
// 添加新窗格
const newTerm = this.addPane(splitWrapper, direction);
const newPane = this.terminals.get(newTerm.id).paneElement;
newPane.style.flex = '1 1 0%';
// 为分割线绑定拖拽调节事件
this.setupDividerDrag(divider, currentPane, newPane, direction);
// 刷新自适应尺寸
setTimeout(() => {
@@ -84,6 +93,43 @@ export class TabSplitTree {
}, 50);
}
setupDividerDrag(divider, firstElement, secondElement, direction) {
const isHorizontal = direction === 'horizontal';
const onMouseDown = (e) => {
e.preventDefault();
divider.classList.add('dragging');
const startPos = isHorizontal ? e.clientX : e.clientY;
const firstSize = isHorizontal ? firstElement.offsetWidth : firstElement.offsetHeight;
const secondSize = isHorizontal ? secondElement.offsetWidth : secondElement.offsetHeight;
const onMouseMove = (moveEvent) => {
const currentPos = isHorizontal ? moveEvent.clientX : moveEvent.clientY;
const delta = currentPos - startPos;
const newFirst = firstSize + delta;
const newSecond = secondSize - delta;
if (newFirst > 50 && newSecond > 50) {
firstElement.style.flex = `${newFirst} ${newFirst} 0%`;
secondElement.style.flex = `${newSecond} ${newSecond} 0%`;
this.fitAll();
}
};
const onMouseUp = () => {
divider.classList.remove('dragging');
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
this.fitAll();
};
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
};
divider.addEventListener('mousedown', onMouseDown);
}
setActiveTerminal(termView) {
this.activeTerminal = termView;
@@ -110,8 +156,30 @@ export class TabSplitTree {
const parent = pane.parentElement;
this.terminals.delete(termId);
// 找到相邻的分割线并移除
const prev = pane.previousElementSibling;
const next = pane.nextElementSibling;
if (prev && prev.classList.contains('split-divider')) {
prev.remove();
} else if (next && next.classList.contains('split-divider')) {
next.remove();
}
pane.remove();
// 如果非根容器中仅剩 1 个有效窗格,消除冗余包裹层将其提升至上一层
if (parent !== this.rootElement && parent.parentElement) {
const remainingChildren = Array.from(parent.children).filter(
(el) => !el.classList.contains('split-divider')
);
if (remainingChildren.length === 1) {
const singleChild = remainingChildren[0];
singleChild.style.flex = parent.style.flex || '1 1 0%';
parent.parentElement.replaceChild(singleChild, parent);
}
}
if (this.terminals.size === 0) {
this.onEmpty();
return;
@@ -121,7 +189,7 @@ export class TabSplitTree {
const nextTerm = this.terminals.values().next().value.termView;
this.setActiveTerminal(nextTerm);
nextTerm.focus();
this.fitAll();
setTimeout(() => this.fitAll(), 40);
}
fitAll() {