fix: nested split pane flex squeezing and add divider drag resize
This commit is contained in:
@@ -14,8 +14,11 @@
|
|||||||
|
|
||||||
.split-container {
|
.split-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1 1 0%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -29,14 +32,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.split-pane {
|
.split-pane {
|
||||||
flex: 1;
|
flex: 1 1 0%;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||||
min-width: 120px;
|
min-width: 0;
|
||||||
min-height: 80px;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-pane.focused {
|
.split-pane.focused {
|
||||||
@@ -49,6 +52,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xterm {
|
.xterm {
|
||||||
@@ -65,19 +69,24 @@
|
|||||||
.split-divider {
|
.split-divider {
|
||||||
background: var(--border-color);
|
background: var(--border-color);
|
||||||
z-index: 10;
|
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 {
|
.split-container.horizontal > .split-divider {
|
||||||
width: 2px;
|
width: 4px;
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-container.vertical > .split-divider {
|
.split-container.vertical > .split-divider {
|
||||||
height: 2px;
|
height: 4px;
|
||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-divider:hover {
|
.split-divider:hover,
|
||||||
|
.split-divider.dragging {
|
||||||
background: var(--accent-color);
|
background: var(--accent-color);
|
||||||
|
box-shadow: 0 0 10px var(--accent-glow);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class TabSplitTree {
|
|||||||
this.onEmpty = options.onEmpty || (() => {});
|
this.onEmpty = options.onEmpty || (() => {});
|
||||||
this.onTitleChange = options.onTitleChange || (() => {});
|
this.onTitleChange = options.onTitleChange || (() => {});
|
||||||
|
|
||||||
this.terminals = new Map(); // id -> TerminalView
|
this.terminals = new Map(); // id -> { termView, paneElement, parentElement }
|
||||||
this.activeTerminal = null;
|
this.activeTerminal = null;
|
||||||
|
|
||||||
this.rootElement = document.createElement('div');
|
this.rootElement = document.createElement('div');
|
||||||
@@ -62,10 +62,14 @@ export class TabSplitTree {
|
|||||||
const currentPane = currentInfo.paneElement;
|
const currentPane = currentInfo.paneElement;
|
||||||
const parent = currentPane.parentElement;
|
const parent = currentPane.parentElement;
|
||||||
|
|
||||||
// 创建一个新的 split-container 容器替换原有 pane,实现嵌套分屏
|
// 创建一个新的 split-container 容器替换原有 pane,实现嵌套等比分屏
|
||||||
const splitWrapper = document.createElement('div');
|
const splitWrapper = document.createElement('div');
|
||||||
splitWrapper.className = `split-container ${direction}`;
|
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);
|
parent.replaceChild(splitWrapper, currentPane);
|
||||||
splitWrapper.appendChild(currentPane);
|
splitWrapper.appendChild(currentPane);
|
||||||
|
|
||||||
@@ -76,6 +80,11 @@ export class TabSplitTree {
|
|||||||
|
|
||||||
// 添加新窗格
|
// 添加新窗格
|
||||||
const newTerm = this.addPane(splitWrapper, direction);
|
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(() => {
|
setTimeout(() => {
|
||||||
@@ -84,6 +93,43 @@ export class TabSplitTree {
|
|||||||
}, 50);
|
}, 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) {
|
setActiveTerminal(termView) {
|
||||||
this.activeTerminal = termView;
|
this.activeTerminal = termView;
|
||||||
|
|
||||||
@@ -110,8 +156,30 @@ export class TabSplitTree {
|
|||||||
const parent = pane.parentElement;
|
const parent = pane.parentElement;
|
||||||
|
|
||||||
this.terminals.delete(termId);
|
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();
|
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) {
|
if (this.terminals.size === 0) {
|
||||||
this.onEmpty();
|
this.onEmpty();
|
||||||
return;
|
return;
|
||||||
@@ -121,7 +189,7 @@ export class TabSplitTree {
|
|||||||
const nextTerm = this.terminals.values().next().value.termView;
|
const nextTerm = this.terminals.values().next().value.termView;
|
||||||
this.setActiveTerminal(nextTerm);
|
this.setActiveTerminal(nextTerm);
|
||||||
nextTerm.focus();
|
nextTerm.focus();
|
||||||
this.fitAll();
|
setTimeout(() => this.fitAll(), 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
fitAll() {
|
fitAll() {
|
||||||
|
|||||||
Reference in New Issue
Block a user