Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions packages/react/src/PageLayout/usePaneWidth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -635,15 +635,24 @@ describe('usePaneWidth', () => {
vi.stubGlobal('innerWidth', 1000)
window.dispatchEvent(new Event('resize'))

// At this point, attribute is applied but timing depends on throttle behavior
// The key is that it gets cleaned up after
// Attribute should be applied immediately on first resize
expect(refs.paneRef.current?.hasAttribute('data-dragging')).toBe(true)
expect(refs.contentRef.current?.hasAttribute('data-dragging')).toBe(true)

// Wait for throttle to complete via rAF
// Fire another resize event immediately (simulating continuous resize)
vi.stubGlobal('innerWidth', 900)
window.dispatchEvent(new Event('resize'))

// Attribute should still be present (containment stays on during continuous resize)
expect(refs.paneRef.current?.hasAttribute('data-dragging')).toBe(true)
expect(refs.contentRef.current?.hasAttribute('data-dragging')).toBe(true)

// Wait for the debounce timeout (150ms) to complete after resize stops
await act(async () => {
await vi.runAllTimersAsync()
await vi.advanceTimersByTimeAsync(150)
})

// Attribute should be removed after throttle completes
// Attribute should be removed after debounce completes
expect(refs.paneRef.current?.hasAttribute('data-dragging')).toBe(false)
expect(refs.contentRef.current?.hasAttribute('data-dragging')).toBe(false)

Expand All @@ -669,7 +678,11 @@ describe('usePaneWidth', () => {
vi.stubGlobal('innerWidth', 1000)
window.dispatchEvent(new Event('resize'))

// Unmount immediately (may or may not have attributes depending on throttle timing)
// Attribute should be applied
expect(refs.paneRef.current?.hasAttribute('data-dragging')).toBe(true)
expect(refs.contentRef.current?.hasAttribute('data-dragging')).toBe(true)

// Unmount immediately (before debounce timer fires)
unmount()

// Attribute should be cleaned up on unmount regardless of timing
Expand Down
13 changes: 11 additions & 2 deletions packages/react/src/PageLayout/usePaneWidth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,11 @@ export function usePaneWidth({
// Throttle approach for window resize - provides immediate visual feedback for small DOMs
// while still limiting update frequency
const THROTTLE_MS = 16 // ~60fps
const DEBOUNCE_MS = 150 // Delay before removing containment after resize stops
let lastUpdateTime = 0
let pendingUpdate = false
let rafId: number | null = null
let debounceId: ReturnType<typeof setTimeout> | null = null
let isResizing = false

// Apply containment during resize to reduce layout thrashing on large DOMs
Expand All @@ -291,29 +293,36 @@ export function usePaneWidth({
}

const handleResize = () => {
// Apply containment on first resize event (stays applied until resize stops)
startResizeOptimizations()

const now = Date.now()
if (now - lastUpdateTime >= THROTTLE_MS) {
lastUpdateTime = now
syncAll()
endResizeOptimizations()
} else if (!pendingUpdate) {
pendingUpdate = true
rafId = requestAnimationFrame(() => {
pendingUpdate = false
rafId = null
lastUpdateTime = Date.now()
syncAll()
endResizeOptimizations()
})
}

// Debounce the cleanup — remove containment after resize stops
if (debounceId !== null) clearTimeout(debounceId)
debounceId = setTimeout(() => {
debounceId = null
endResizeOptimizations()
}, DEBOUNCE_MS)
}

// eslint-disable-next-line github/prefer-observers -- Uses window resize events instead of ResizeObserver to avoid INP issues. ResizeObserver on document.documentElement fires on any content change (typing, etc), while window resize only fires on actual viewport changes.
window.addEventListener('resize', handleResize)
return () => {
if (rafId !== null) cancelAnimationFrame(rafId)
if (debounceId !== null) clearTimeout(debounceId)
endResizeOptimizations()
window.removeEventListener('resize', handleResize)
}
Expand Down
Loading