feat(ui): 自由窗口——tab 拖出侧边栏成为可移动/缩放/置顶的浮动窗口 (v0.16.0)
- 拖出:标签栏 tab 拖到主会话列(HTML5 DnD 检测 + 虚线提示浮层)或右键菜单 「移动到自由窗口」(右/底面板共用 TabBar),tab 从 pane 移动进 SidebarState.floats - 窗口(FreeWindow,渲染于 [data-dsh-panel-host] z-42):头部 pointer 拖动 (rAF 直写 + 松手提交)、拖到 pane 高亮停靠(center 合并)、SE 角缩放、 点击置顶、头部右键(回到侧边栏/关闭)、X = closeTab 走正常生命周期 - 服务语义:openTab dedupe/id 聚焦浮动 tab = 置顶窗口(不展开面板); closeTab/activateTab 兼顾 floats;agent 终端 reconcile 覆盖浮窗; features 单调新增 'floatWindows',版本 0.16.0 - 持久化:floats 随会话 localStorage,sanitizeState 宽容校验 + 几何钳制, uid 播种覆盖 float 前缀;i18n 三键 19 语言同步 - 关键修复(e2e 发现):portal 覆盖层(头部 Menu)的合成事件沿 React 树 冒泡回拖拽表面,劫持菜单行点击——拖拽起点加 DOM 同源守卫(jsdom 不复现) - e2e-mount.sh:缺省 tarball 改取 mtime 最新(旧字典序选择会把冒烟挂到 过期产物);新增 tests/e2e/float-window.e2e.ts 双用例
This commit is contained in:
@@ -282,6 +282,141 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
/* ── Free windows ──────────────────────────────────────────────────────── */
|
||||
|
||||
/* One floating tab: the same card surface as the panels, at viewport
|
||||
coordinates inside the panel host (immune to shell transforms). z-index
|
||||
42: above the panels (40) so a window drags across them, below the
|
||||
toggle cluster (45) and the app's overlay stack (100+, issue #52).
|
||||
Stacking among windows is the floats array order (siblings share the
|
||||
z-index; DOM order wins). Deliberately NO box transition — the header/
|
||||
resize drags write left/top/width/height per frame. Elevated surface,
|
||||
so unlike the flat panels it earns a shadow token. */
|
||||
.floatWindow {
|
||||
position: absolute;
|
||||
z-index: 42;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Re-enable interaction inside the click-transparent panel host. */
|
||||
pointer-events: auto;
|
||||
background: var(--dsw-alias-bg-layer-1);
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--dsw-shadow-lv3);
|
||||
overflow: hidden;
|
||||
contain: layout style;
|
||||
}
|
||||
|
||||
.floatWindowDragging {
|
||||
will-change: left, top, width, height;
|
||||
}
|
||||
|
||||
/* The header: the move/dock-back drag handle, mirroring the tab strip's
|
||||
chrome (34px, hairline bottom, secondary label). */
|
||||
.floatHeader {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
height: 34px;
|
||||
padding: 0 4px 0 10px;
|
||||
font: var(--dsw-font-xxs-12);
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
background: var(--dsw-alias-bg-layer-1);
|
||||
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.floatWindowDragging .floatHeader {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.floatTitle {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Mirrors .tabClose. */
|
||||
.floatClose {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.floatClose:hover {
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.floatContent {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* The SE resize corner (mirror of .cornerHandle). */
|
||||
.floatResize {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
z-index: 2;
|
||||
cursor: nwse-resize;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.floatResize:hover {
|
||||
background: var(--dsw-alias-interactive-bg-hover-accent);
|
||||
}
|
||||
|
||||
/* A pane highlighted as the dock target while a float header drag hovers it
|
||||
(the attribute is written directly by the drag at pointer cadence;
|
||||
skin-addressable via [data-dsh-pane][data-dsh-float-dock-over]). */
|
||||
.pane[data-dsh-float-dock-over] {
|
||||
outline: 2px dashed var(--dsw-alias-interactive-bg-hover-accent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* The drag-out hint over the conversation column: a dashed frame marking
|
||||
the drop zone, pointer-transparent so it never disturbs the drag it
|
||||
describes. z-index 46: above the windows (42) for unambiguous feedback,
|
||||
still below the app's overlay stack. */
|
||||
.floatDropHint {
|
||||
position: absolute;
|
||||
z-index: 46;
|
||||
pointer-events: none;
|
||||
border: 2px dashed var(--dsw-alias-interactive-bg-hover-accent);
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--dsw-alias-interactive-bg-hover-accent) 12%, transparent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.floatDropHintLabel {
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
font: var(--dsw-font-xxs-12);
|
||||
color: var(--dsw-alias-label-primary);
|
||||
background: var(--dsw-alias-bg-layer-1);
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
}
|
||||
|
||||
/* ── Desktop drag-region opt-out ──────────────────────────────────────── */
|
||||
|
||||
/* Frameless desktop shells make the top strip of the web content draggable
|
||||
@@ -293,7 +428,8 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
|
||||
browsers and in shells without drag regions — safe everywhere. */
|
||||
.toggleCluster,
|
||||
.toggleButton,
|
||||
.tabBar {
|
||||
.tabBar,
|
||||
.floatHeader {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user