diff --git a/packages/react/src/PageLayout/PageLayout.test.tsx b/packages/react/src/PageLayout/PageLayout.test.tsx index 5d841b799de..2bdf5136539 100644 --- a/packages/react/src/PageLayout/PageLayout.test.tsx +++ b/packages/react/src/PageLayout/PageLayout.test.tsx @@ -234,7 +234,7 @@ describe('PageLayout', async () => { expect(content!.style.getPropertyValue('contain')).toBe('') }) - it('should add will-change during drag for optimized updates', async () => { + it('should apply containment optimizations during drag', async () => { const {container} = render( @@ -249,15 +249,89 @@ describe('PageLayout', async () => { const pane = container.querySelector('[class*="Pane"][data-resizable]') const divider = await screen.findByRole('slider') - // Before drag - no will-change - expect(pane!.style.willChange).toBe('') + // Before drag - no containment + expect(pane!.style.contain).toBe('') + expect(pane!.style.pointerEvents).toBe('') - // Start drag - will-change is added + // Start drag - containment is added fireEvent.pointerDown(divider, {clientX: 300, clientY: 200, pointerId: 1}) - expect(pane!.style.willChange).toBe('width') - // End drag - will-change is removed + expect(pane!.style.contain).toBe('layout style paint') + expect(pane!.style.pointerEvents).toBe('none') + + // End drag - containment is removed + fireEvent.lostPointerCapture(divider, {pointerId: 1}) + expect(pane!.style.contain).toBe('') + expect(pane!.style.pointerEvents).toBe('') + }) + + it('should apply content-visibility only for tall content during drag', async () => { + const {container} = render( + + + + + + + + , + ) + + const content = container.querySelector('[class*="Content"]') + const divider = await screen.findByRole('slider') + + // Mock offsetHeight for tall content (>1000px threshold) + Object.defineProperty(content, 'offsetHeight', { + configurable: true, + value: 1200, + }) + + // Before drag - no content-visibility + expect(content!.style.contentVisibility).toBe('') + expect(content!.style.containIntrinsicSize).toBe('') + + // Start drag - content-visibility is added for tall content + fireEvent.pointerDown(divider, {clientX: 300, clientY: 200, pointerId: 1}) + expect(content!.style.contentVisibility).toBe('auto') + expect(content!.style.containIntrinsicSize).toBe('auto 1200px') + + // End drag - content-visibility is removed + fireEvent.lostPointerCapture(divider, {pointerId: 1}) + expect(content!.style.contentVisibility).toBe('') + expect(content!.style.containIntrinsicSize).toBe('') + }) + + it('should not apply content-visibility for short content during drag', async () => { + const {container} = render( + + + + + + + + , + ) + + const content = container.querySelector('[class*="Content"]') + const divider = await screen.findByRole('slider') + + // Mock offsetHeight for short content (<1000px threshold) + Object.defineProperty(content, 'offsetHeight', { + configurable: true, + value: 640, + }) + + // Start drag + fireEvent.pointerDown(divider, {clientX: 300, clientY: 200, pointerId: 1}) + + // content-visibility should NOT be applied for short content + expect(content!.style.contentVisibility).toBe('') + expect(content!.style.containIntrinsicSize).toBe('') + // But basic containment should still be applied + expect(content!.style.contain).toBe('layout style paint') + + // End drag fireEvent.lostPointerCapture(divider, {pointerId: 1}) - expect(pane!.style.willChange).toBe('') }) }) diff --git a/packages/react/src/PageLayout/paneUtils.ts b/packages/react/src/PageLayout/paneUtils.ts index 4ac2ebca89b..9ebfa8483dd 100644 --- a/packages/react/src/PageLayout/paneUtils.ts +++ b/packages/react/src/PageLayout/paneUtils.ts @@ -1,3 +1,9 @@ +/** + * Height threshold (in pixels) above which content-visibility optimizations are applied. + * Avoids overhead on small content that doesn't benefit from rendering optimizations. + */ +const TALL_CONTENT_THRESHOLD = 1000 + /** * Apply CSS containment optimizations to isolate an element during resize/drag. * - contain: limits layout/paint recalc to this subtree @@ -8,9 +14,14 @@ export function setContainmentOptimizations(element: HTMLElement | null) { if (!element) return element.style.contain = 'layout style paint' - element.style.contentVisibility = 'auto' - element.style.containIntrinsicSize = 'auto 500px' element.style.pointerEvents = 'none' + + // Only apply content-visibility for tall content to avoid overhead on small elements + const height = element.offsetHeight + if (height > TALL_CONTENT_THRESHOLD) { + element.style.contentVisibility = 'auto' + element.style.containIntrinsicSize = `auto ${height}px` + } } /** @@ -32,13 +43,29 @@ type DraggingStylesParams = { /** Apply visual feedback and performance optimizations during drag */ export function setDraggingStyles({handle, pane, content}: DraggingStylesParams) { + // Handle visual feedback handle?.style.setProperty('background-color', 'var(--bgColor-accent-emphasis)') handle?.style.setProperty('--draggable-handle--drag-opacity', '1') // Disable transition for instant visual feedback during drag handle?.style.setProperty('--draggable-handle--transition', 'none') - pane?.style.setProperty('will-change', 'width') - setContainmentOptimizations(content) - setContainmentOptimizations(pane) + + // Pane: minimal containment (always visible during drag) + if (pane) { + pane.style.contain = 'layout style paint' + pane.style.pointerEvents = 'none' + } + + // Content: containment + conditional content-visibility for tall content + if (content) { + content.style.contain = 'layout style paint' + content.style.pointerEvents = 'none' + + const height = content.offsetHeight + if (height > TALL_CONTENT_THRESHOLD) { + content.style.contentVisibility = 'auto' + content.style.containIntrinsicSize = `auto ${height}px` + } + } } /** Remove drag styles and restore normal state */ @@ -46,7 +73,16 @@ export function removeDraggingStyles({handle, pane, content}: DraggingStylesPara handle?.style.removeProperty('background-color') handle?.style.removeProperty('--draggable-handle--drag-opacity') handle?.style.removeProperty('--draggable-handle--transition') - pane?.style.removeProperty('will-change') - removeContainmentOptimizations(content) - removeContainmentOptimizations(pane) + + if (pane) { + pane.style.contain = '' + pane.style.pointerEvents = '' + } + + if (content) { + content.style.contain = '' + content.style.pointerEvents = '' + content.style.contentVisibility = '' + content.style.containIntrinsicSize = '' + } }