fix: 面板拖动布局闪烁 — 右侧栏关闭时底部高度拖动挤压 AppFrame + 释放瞬间整体铺满再回弹

- applyDrag 仅在右侧面板打开时写入 --dsh-sidebar-width(底部高度拖动传入的是右侧栏持久化宽度,关闭时会把整个 AppFrame 左挤,直到松手才恢复)
- 布局 push effect 拆分为「仅设置」+「仅卸载移除」:React 同一提交内先执行全部 cleanup 再执行全部 setup,原 cleanup 删除 CSS 变量后,draggingRef effect 的 measureCenter 强制重算把 0px 缓存为 margin-right 过渡起点,释放时过渡重新启用 → 0→W 动画(先铺满页面再回弹)
- 回归测试:size change 提交过程中不得 removeProperty 布局变量(仅卸载时移除,issue #31 语义保留)
This commit is contained in:
ChenRuoT
2026-08-17 23:24:29 +08:00
parent 0b5405117d
commit bb8df17ecb
2 changed files with 60 additions and 10 deletions
+31 -1
View File
@@ -26,7 +26,7 @@ import { act } from 'react-dom/test-utils'
;(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true
import { Sidebar } from '../src/client/Sidebar.tsx'
import { createSidebarStore, type SidebarStore } from '../src/client/state.ts'
import { createSidebarStore, setBottomHeight, toggleBottomPanel, type SidebarStore } from '../src/client/state.ts'
import { createBetterSidebarService, type BetterSidebarService } from '../src/client/service.ts'
import { t } from '../src/client/locales.ts'
@@ -99,6 +99,36 @@ describe('layout-push variable cleanup', () => {
expect(htmlStyle.getPropertyValue('--dsh-sidebar-width')).toBe('')
expect(htmlStyle.getPropertyValue('--dsh-sidebar-height')).toBe('')
})
it('a size change (release commit) re-applies the variables without removing them mid-commit', () => {
const { store, unmount } = mountSidebar()
const htmlStyle = document.documentElement.style
const width = store.getSnapshot().state!.width
const removeSpy = vi.spyOn(htmlStyle, 'removeProperty')
// Simulate a drag release: the bottom panel opens and its height commits.
act(() => { store.reduce(toggleBottomPanel) })
act(() => { store.reduce(s => setBottomHeight(s, 300)) })
expect(htmlStyle.getPropertyValue('--dsh-sidebar-width')).toBe(`${width}px`)
expect(htmlStyle.getPropertyValue('--dsh-sidebar-height')).toBe('300px')
// The commit must NOT have removed the variables at any point. React
// runs every effect cleanup before every effect setup in a commit, so a
// cleanup here would remove the variables before the draggingRef effect
// (declared above the layout push) forces a layout in measureCenter
// (getBoundingClientRect). That forced recalc resolves #root's
// margin-right to the 0px fallback, caches it as the transition start
// value, and measures centerRect too wide; once transitions re-enable on
// release, the shell animates margin-right 0 → width — the "expand to
// the full page then bounce back" flash.
const removalProps = removeSpy.mock.calls
.map(call => call[0] as string)
.filter(prop => prop === '--dsh-sidebar-width' || prop === '--dsh-sidebar-height')
expect(removalProps).toHaveLength(0)
// Only unmounting may remove them (issue #31).
unmount()
expect(removeSpy.mock.calls.some(call =>
call[0] === '--dsh-sidebar-width' || call[0] === '--dsh-sidebar-height')).toBe(true)
removeSpy.mockRestore()
})
})
describe('tab crash containment', () => {