Skip to content

Commit fad6186

Browse files
committed
fix(chat): retain resized panels when pointer capture ends
1 parent 02cd2f6 commit fad6186

2 files changed

Lines changed: 51 additions & 44 deletions

File tree

‎apps/desktop/e2e/chat-panel.spec.ts‎

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -253,39 +253,50 @@ test('chat panel sizes survive navigation, chat switches, collapse, and layout c
253253
'detach',
254254
'chat switch',
255255
] as const) {
256-
await check(`${interruption} keeps the previous saved width`, async () => {
257-
const before = await page.evaluate(() => JSON.stringify(localStorage))
258-
const rect = await beginDrag()
259-
await page.mouse.move(rect.x + 100, rect.y + 100, { steps: 12 })
260-
await expectWidth(598)
261-
if (interruption === 'pointercancel') {
262-
await divider.dispatchEvent('pointercancel', { pointerId: 1 })
263-
} else if (interruption === 'capture loss') {
264-
await divider.evaluate((element) => element.releasePointerCapture(1))
265-
await page.mouse.move(rect.x + 101, rect.y + 100)
266-
} else if (interruption === 'blur') {
267-
await page.evaluate(() => window.dispatchEvent(new Event('blur')))
268-
} else if (interruption === 'chat switch') {
269-
await page
270-
.getByRole('button', { name: 'organization-chat-a', exact: true })
271-
.evaluate((element: HTMLButtonElement) => element.click())
272-
await expectWidth(560)
273-
} else {
274-
await page
275-
.getByRole('button', { name: 'Settings', exact: true })
276-
.evaluate((element: HTMLButtonElement) => element.click())
277-
await expect(panel).toHaveCount(0)
278-
}
279-
await page.mouse.up()
280-
if (interruption === 'chat switch') {
281-
await page.getByRole('button', { name: 'workspace-chat-b', exact: true }).click()
282-
}
283-
if (interruption === 'detach') {
284-
await page.getByRole('button', { name: 'Back', exact: true }).click()
256+
const keepsDraggedWidth = interruption === 'capture loss' || interruption === 'blur'
257+
await check(
258+
`${interruption} keeps the ${keepsDraggedWidth ? 'dragged' : 'previous saved'} width`,
259+
async () => {
260+
const before = await page.evaluate(() => JSON.stringify(localStorage))
261+
const rect = await beginDrag()
262+
await page.mouse.move(rect.x + 100, rect.y + 100, { steps: 12 })
263+
await expectWidth(598)
264+
if (interruption === 'pointercancel') {
265+
await divider.dispatchEvent('pointercancel', { pointerId: 1 })
266+
} else if (interruption === 'capture loss') {
267+
await divider.evaluate((element) => element.releasePointerCapture(1))
268+
await page.mouse.move(rect.x + 100, rect.y + 100)
269+
} else if (interruption === 'blur') {
270+
await page.evaluate(() => window.dispatchEvent(new Event('blur')))
271+
} else if (interruption === 'chat switch') {
272+
await page
273+
.getByRole('button', { name: 'organization-chat-a', exact: true })
274+
.evaluate((element: HTMLButtonElement) => element.click())
275+
await expectWidth(560)
276+
} else {
277+
await page
278+
.getByRole('button', { name: 'Settings', exact: true })
279+
.evaluate((element: HTMLButtonElement) => element.click())
280+
await expect(panel).toHaveCount(0)
281+
}
282+
await page.mouse.up()
283+
if (interruption === 'chat switch') {
284+
await page.getByRole('button', { name: 'workspace-chat-b', exact: true }).click()
285+
}
286+
if (interruption === 'detach') {
287+
await page.getByRole('button', { name: 'Back', exact: true }).click()
288+
}
289+
await expectWidth(keepsDraggedWidth ? 598 : 698)
290+
if (keepsDraggedWidth) {
291+
await page.reload()
292+
await page.getByRole('button', { name: 'workspace-chat-b', exact: true }).click()
293+
await expectWidth(598)
294+
await dragTo(698)
295+
} else {
296+
expect(await page.evaluate(() => JSON.stringify(localStorage))).toBe(before)
297+
}
285298
}
286-
await expectWidth(698)
287-
expect(await page.evaluate(() => JSON.stringify(localStorage))).toBe(before)
288-
})
299+
)
289300
}
290301

291302
await check('a viewport change during a drag clamps the committed display width', async () => {

‎apps/sim/app/workspace/[workspaceId]/home/hooks/use-mothership-resize.ts‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ export function useMothershipResize(
285285
const cancelPointer = (event: PointerEvent) => {
286286
if (event.pointerId === pointerId) cancel()
287287
}
288+
const complete = () => finish(true)
289+
const completePointer = (event: PointerEvent) => {
290+
if (event.pointerId === pointerId) complete()
291+
}
288292
cleanupRef.current = cancel
289293

290294
handle.addEventListener(
@@ -307,22 +311,14 @@ export function useMothershipResize(
307311
{ signal }
308312
)
309313

310-
handle.addEventListener(
311-
'pointerup',
312-
(upEvent: PointerEvent) => {
313-
if (upEvent.pointerId !== pointerId) return
314-
finish(true)
315-
},
316-
{ signal }
317-
)
314+
handle.addEventListener('pointerup', completePointer, { signal })
318315

319316
// Browser fires pointercancel when it reclaims the gesture (scroll, palm rejection, etc.)
320317
// Without this, body cursor/userSelect and transition would be permanently stuck
321318
handle.addEventListener('pointercancel', cancelPointer, { signal })
322-
handle.addEventListener('lostpointercapture', cancelPointer, { signal })
323-
// A blur mid-drag (cmd-tab, window switch) would otherwise strand the
324-
// body cursor/userSelect overrides with no pointerup coming
325-
window.addEventListener('blur', cancel, { signal })
319+
// Capture or focus can be lost before pointerup reaches the handle; retain the user's resize.
320+
handle.addEventListener('lostpointercapture', completePointer, { signal })
321+
window.addEventListener('blur', complete, { signal })
326322
},
327323
[desktopScopeId, rememberWidth]
328324
)

0 commit comments

Comments
 (0)