From 1125324233e261a9b026c5a11e324156b8979e8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 19:40:51 +0000 Subject: [PATCH 1/2] Initial plan From 7d09acbb7085f5529b059854a25f6769347feac4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 19:55:21 +0000 Subject: [PATCH 2/2] Add cleanup for dragging styles on unmount mid-drag - Cache DOM element references in DragHandle for cleanup - Update cleanup effect to remove dragging styles if unmounting mid-drag - Add test to verify dragging styles are cleaned up on unmount - Refs from context get cleared during unmount, so cache elements when drag starts Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com> --- .../react/src/PageLayout/PageLayout.test.tsx | 29 +++++++++++++++++++ packages/react/src/PageLayout/PageLayout.tsx | 19 ++++++++++++ 2 files changed, 48 insertions(+) diff --git a/packages/react/src/PageLayout/PageLayout.test.tsx b/packages/react/src/PageLayout/PageLayout.test.tsx index 5f022c5aea6..9d640f0fb8c 100644 --- a/packages/react/src/PageLayout/PageLayout.test.tsx +++ b/packages/react/src/PageLayout/PageLayout.test.tsx @@ -259,6 +259,35 @@ describe('PageLayout', async () => { fireEvent.lostPointerCapture(divider, {pointerId: 1}) expect(pane!.style.willChange).toBe('') }) + + it('should cleanup dragging styles on unmount mid-drag', async () => { + const {container, unmount} = render( + + + + + + + + , + ) + + const pane = container.querySelector('[class*="Pane"][data-resizable]') + const content = container.querySelector('[class*="PageLayoutContent"]') + const divider = await screen.findByRole('slider') + + // Start drag + fireEvent.pointerDown(divider, {clientX: 300, clientY: 200, pointerId: 1}) + expect(pane).toHaveAttribute('data-dragging', 'true') + expect(content).toHaveAttribute('data-dragging', 'true') + + // Unmount mid-drag + unmount() + + // Attributes should be cleaned up + expect(pane).not.toHaveAttribute('data-dragging') + expect(content).not.toHaveAttribute('data-dragging') + }) }) describe('PageLayout.Content', () => { diff --git a/packages/react/src/PageLayout/PageLayout.tsx b/packages/react/src/PageLayout/PageLayout.tsx index 943c9561d50..2d17911817f 100644 --- a/packages/react/src/PageLayout/PageLayout.tsx +++ b/packages/react/src/PageLayout/PageLayout.tsx @@ -244,10 +244,19 @@ const DragHandle = memo(function DragHandle({ // Dragging state as a ref - cheaper than reading from DOM style const isDraggingRef = React.useRef(false) + + // Cache DOM elements for cleanup - refs from context get cleared during unmount + const cachedHandleRef = React.useRef(null) + const cachedPaneRef = React.useRef(null) + const cachedContentRef = React.useRef(null) // Set inline styles for drag optimizations - zero overhead at rest const startDragging = React.useCallback(() => { if (isDraggingRef.current) return + // Cache current element references for cleanup + cachedHandleRef.current = handleRef.current + cachedPaneRef.current = paneRef.current + cachedContentRef.current = contentRef.current setDraggingStyles({ handle: handleRef.current, pane: paneRef.current, @@ -378,10 +387,20 @@ const DragHandle = memo(function DragHandle({ // Cleanup rAF on unmount to prevent stale callbacks React.useEffect(() => { return () => { + // Cancel pending rAF if (rafIdRef.current !== null) { cancelAnimationFrame(rafIdRef.current) rafIdRef.current = null } + // Clean up dragging state if unmounting mid-drag + // Use cached element references since refs from context get cleared during unmount + if (isDraggingRef.current) { + removeDraggingStyles({ + handle: cachedHandleRef.current, + pane: cachedPaneRef.current, + content: cachedContentRef.current, + }) + } } }, [])