Skip to content
Closed
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
49 changes: 41 additions & 8 deletions packages/react/src/PageLayout/PageLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<PageLayout>
<PageLayout.Pane resizable>
Expand All @@ -247,17 +247,50 @@ describe('PageLayout', async () => {
)

const pane = container.querySelector<HTMLElement>('[class*="Pane"][data-resizable]')
const content = container.querySelector<HTMLElement>('[class*="PageLayoutContent"]')
const divider = await screen.findByRole('slider')

// Before drag - no will-change
expect(pane!.style.willChange).toBe('')

// Start drag - will-change is added
// Mock offsetHeight for testing
Object.defineProperty(pane, 'offsetHeight', {
configurable: true,
value: 320,
})
Object.defineProperty(content, 'offsetHeight', {
configurable: true,
value: 640,
})

// Before drag - no containment
expect(pane!.style.contain).toBe('')
expect(pane!.style.pointerEvents).toBe('')
expect(pane!.style.contentVisibility).toBe('')
expect(pane!.style.containIntrinsicSize).toBe('')
expect(content!.style.contain).toBe('')
expect(content!.style.pointerEvents).toBe('')
expect(content!.style.contentVisibility).toBe('')
expect(content!.style.containIntrinsicSize).toBe('')

// Start drag - containment and content-visibility are added to both pane and content
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')
expect(pane!.style.contentVisibility).toBe('auto')
expect(pane!.style.containIntrinsicSize).toBe('auto 320px')
expect(content!.style.contain).toBe('layout style paint')
expect(content!.style.pointerEvents).toBe('none')
expect(content!.style.contentVisibility).toBe('auto')
expect(content!.style.containIntrinsicSize).toBe('auto 640px')

// End drag - containment is removed
fireEvent.lostPointerCapture(divider, {pointerId: 1})
expect(pane!.style.willChange).toBe('')
expect(pane!.style.contain).toBe('')
expect(pane!.style.pointerEvents).toBe('')
expect(pane!.style.contentVisibility).toBe('')
expect(pane!.style.containIntrinsicSize).toBe('')
expect(content!.style.contain).toBe('')
expect(content!.style.pointerEvents).toBe('')
expect(content!.style.contentVisibility).toBe('')
expect(content!.style.containIntrinsicSize).toBe('')
})
})

Expand Down
14 changes: 6 additions & 8 deletions packages/react/src/PageLayout/paneUtils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/**
* Apply CSS containment optimizations to isolate an element during resize/drag.
* - contain: limits layout/paint recalc to this subtree
* - content-visibility: skip rendering off-screen content (valuable for large DOMs)
* - contain-intrinsic-size: prevents layout thrashing from size estimation when using content-visibility
* - content-visibility: skip rendering off-screen content
* - contain-intrinsic-size: uses actual element height to prevent layout shift
* - pointer-events: skip hit-testing large child trees
*/
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.containIntrinsicSize = `auto ${element.offsetHeight}px`
element.style.pointerEvents = 'none'
}

Expand All @@ -34,19 +34,17 @@ type DraggingStylesParams = {
export function setDraggingStyles({handle, pane, content}: DraggingStylesParams) {
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)
// No will-change: width - doesn't help layout properties
setContainmentOptimizations(pane)
setContainmentOptimizations(content)
}

/** Remove drag styles and restore normal state */
export function removeDraggingStyles({handle, pane, content}: DraggingStylesParams) {
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)
removeContainmentOptimizations(content)
}
Loading