From ffcd6953ebdac68c2ecf17b52a59ba94c88e743a Mon Sep 17 00:00:00 2001 From: Ch3n4y Date: Thu, 20 Aug 2026 21:11:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(layout):=20=E9=9D=A2=E6=9D=BF=E5=AE=BF?= =?UTF-8?q?=E4=B8=BB=E8=A3=81=E5=89=AA=E8=A7=86=E5=8F=A3=E8=BE=B9=E7=BC=98?= =?UTF-8?q?=EF=BC=8C=E6=94=B6=E8=B5=B7=E7=9A=84=E9=9D=A2=E6=9D=BF=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E6=92=91=E5=87=BA=E6=96=87=E6=A1=A3=E5=8F=8C=E5=90=91?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8=20(#277)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 收起的面板以 transform: translate(102%) 滑出屏幕,但 transform 后的元素仍计入祖先滚动溢出区,导致整个页面可左右/上下拖拽。面板宿主 [data-dsh-panel-host] 是视口尺寸的 fixed 容器,补上 overflow: hidden 后裁剪点恰在视口边缘,开合滑入动画、内部滚动容器与 fixed 弹层均不受影响。e2e 增加回归断言:面板收起时文档 scrollWidth/Height 不得超出视口。 --- src/client/sidebar.module.css | 9 +++++++++ tests/e2e/mount.e2e.ts | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/client/sidebar.module.css b/src/client/sidebar.module.css index 3dcb15c..3459b4f 100644 --- a/src/client/sidebar.module.css +++ b/src/client/sidebar.module.css @@ -34,6 +34,15 @@ inset: 0; z-index: 40; pointer-events: none; + /* Clip at the viewport edge: collapsed panels slide off-screen with + `transform: translate(102%)`, and a transformed element still + contributes to its ancestors' scrollable overflow — without this the + hidden panels extended the document's scrollable area, making the whole + page draggable left-right and up-down while any panel was collapsed. + The host is exactly viewport-sized (inset: 0), so the clip is invisible + until a panel slides in from the edge; internal scroll containers and + the app's fixed overlay stack are unaffected. */ + overflow: hidden; } :global([data-dsh-panel-host][data-dsh-panel-host-degraded]) { diff --git a/tests/e2e/mount.e2e.ts b/tests/e2e/mount.e2e.ts index a7dbf2c..46bc5c9 100644 --- a/tests/e2e/mount.e2e.ts +++ b/tests/e2e/mount.e2e.ts @@ -175,6 +175,26 @@ test('plugin mounts into the DSH shell and survives a built-in tab sweep', async const tabBar = sidebar.locator('[title]') await expect(tabBar.first()).toBeAttached({ timeout: 90_000 }) + // Regression: with the panels still COLLAPSED, the document must not grow + // beyond the viewport. Collapsed panels are slid off-screen with + // `transform: translate(102%)`, and a transformed element still + // contributes to its ancestors' scrollable overflow — unclipped, the + // hidden panels extended the document's scroll area and the whole page + // became draggable left-right and up-down. The panel host clips at the + // viewport edge (overflow: hidden), so scrollWidth/scrollHeight must stay + // within the viewport here. (Assert <= not == : a classic scrollbar + // narrows clientWidth, so scrollWidth may sit slightly under innerWidth.) + await expect + .poll( + () => + page.evaluate(() => ({ + overflowX: document.documentElement.scrollWidth <= window.innerWidth, + overflowY: document.documentElement.scrollHeight <= window.innerHeight, + })), + { timeout: 30_000 }, + ) + .toEqual({ overflowX: true, overflowY: true }) + // openByDefault defaults OFF: a fresh session's panel starts collapsed. // Expand it through the toggle cluster before the layout push can apply. const expandButton = sidebar.getByRole('button', { name: 'Expand sidebar' })