fix(desktop): restore drag previews before the release commit folds a zone
When a sash drag folds a tool zone to its rail, the store commit re-renders only the wrappers whose style prop changed; the flex sibling the gesture had pinned to `flex: 0 1 <px>` kept that inline preview and could not grow into the freed space. Restore every captured style attribute before the commit, on every release path.
This commit is contained in:
+50
@@ -76,6 +76,13 @@ function setWidth(element: HTMLElement, width: number) {
|
||||
Object.defineProperty(element, 'getBoundingClientRect', { configurable: true, value: () => rect(width) })
|
||||
}
|
||||
|
||||
function setHeight(element: HTMLElement, height: number) {
|
||||
Object.defineProperty(element, 'getBoundingClientRect', {
|
||||
configurable: true,
|
||||
value: () => ({ ...rect(1000), bottom: height, height })
|
||||
})
|
||||
}
|
||||
|
||||
function row(): SplitNode {
|
||||
const tree = $layoutTree.get()
|
||||
|
||||
@@ -180,4 +187,47 @@ describe('TreeSplit cascading expansion', () => {
|
||||
expect(row().weights[0]).toBeCloseTo(2.2)
|
||||
expect(row().children[3]).toMatchObject({ id: 'terminal-zone', minimized: true })
|
||||
})
|
||||
it('folding a tool zone at its floor leaves no drag preview pinned on the flex sibling', () => {
|
||||
markCollapsePane('terminal')
|
||||
disposers.push(
|
||||
registry.register({
|
||||
area: 'panes',
|
||||
data: { height: '200px', placement: 'bottom' },
|
||||
id: 'terminal',
|
||||
render: () => null,
|
||||
title: 'Terminal'
|
||||
})
|
||||
)
|
||||
|
||||
const tree = split(
|
||||
'column',
|
||||
[group(['chat'], { id: 'chat-zone' }), group(['terminal'], { id: 'terminal-zone' })],
|
||||
[1, 1],
|
||||
'root-column'
|
||||
)
|
||||
|
||||
$layoutTree.set(tree)
|
||||
|
||||
render(<TreeSplit node={tree} root />)
|
||||
|
||||
const container = document.querySelector<HTMLElement>('[data-tree-split="root-column"]')!
|
||||
const [chat, terminal] = [...container.children] as HTMLElement[]
|
||||
setHeight(container, 800)
|
||||
setHeight(chat, 600)
|
||||
setHeight(terminal, 200)
|
||||
setHeight(document.querySelector<HTMLElement>('[data-tree-group="terminal-zone"]')!, 200)
|
||||
|
||||
const chatFlex = chat.style.flex
|
||||
const terminalSash = document.querySelectorAll('[role="separator"]')[0]!
|
||||
fireEvent.pointerDown(terminalSash, { button: 0, clientY: 600, pointerId: 1, pointerType: 'mouse' })
|
||||
fireEvent.pointerMove(window, { clientY: 790, pointerId: 1, pointerType: 'mouse' })
|
||||
fireEvent.pointerUp(window, { clientY: 790, pointerId: 1, pointerType: 'mouse' })
|
||||
|
||||
// The zone folds to its rail (no sliver persisted) and the chat wrapper —
|
||||
// which the commit does not re-render — is back on React's own flex, not
|
||||
// the `0 1 <px>` pin the gesture previewed.
|
||||
expect(row().children[1]).toMatchObject({ id: 'terminal-zone', minimized: true })
|
||||
expect($paneStates.get().terminal?.heightOverride).toBeUndefined()
|
||||
expect(chat.style.flex).toBe(chatFlex)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -387,10 +387,8 @@ export function TreeSplit({ node, root, rootRow }: { node: SplitNode; root?: boo
|
||||
// - every visible flex track is pinned to `0 1 <px>`. The plan keeps
|
||||
// the run's total px constant, so no leftover gap can open.
|
||||
// - hidden (display:none) and minimized tracks are left untouched.
|
||||
// - cleanup: a real drag commits the store once, and React's re-render
|
||||
// rewrites the `flex` shorthand, which clears the overrides (writing
|
||||
// the shorthand resets the longhands). A no-movement click restores
|
||||
// the captured style attributes instead, since nothing re-renders.
|
||||
// - cleanup: release restores the captured style attributes, then a
|
||||
// real drag commits the store once and React re-renders from it.
|
||||
const previewPlan = (plan: ReturnType<typeof planFor>) => {
|
||||
sashTracks.forEach((track, index) => {
|
||||
if (!track.visible || track.minimized) {
|
||||
@@ -458,6 +456,14 @@ export function TreeSplit({ node, root, rootRow }: { node: SplitNode; root?: boo
|
||||
done = true
|
||||
resize.finish()
|
||||
|
||||
// Put every wrapper's inline style back exactly as React last wrote
|
||||
// it BEFORE the store commit. React only rewrites a wrapper whose
|
||||
// style prop changed; a preview pinned on a track the commit leaves
|
||||
// alone (the flex run beside a zone that folded to its rail) would
|
||||
// otherwise survive as a stale `flex: 0 1 <px>` and stop it growing.
|
||||
// A no-movement click has no commit, so this is also its whole cleanup.
|
||||
restoreStyles()
|
||||
|
||||
if (lastPlan && lastPlan.moved !== 0) {
|
||||
// Dragged a tool panel down to its collapsed header? Fold the zone
|
||||
// to its rail instead of persisting a sliver — and DON'T write the
|
||||
@@ -473,10 +479,6 @@ export function TreeSplit({ node, root, rootRow }: { node: SplitNode; root?: boo
|
||||
} else {
|
||||
commitPlan(lastPlan)
|
||||
}
|
||||
} else {
|
||||
// Click without movement: nothing will re-render, so put the
|
||||
// wrappers' inline styles back exactly as React last wrote them.
|
||||
restoreStyles()
|
||||
}
|
||||
|
||||
// Geometry vars re-enable AFTER the final store commit above, so the
|
||||
|
||||
Reference in New Issue
Block a user