Skip to content

fix(painter-dom): skip non-scrollable scroll container in virtualization (SD-2199)#2383

Merged
harbournick merged 1 commit intomainfrom
fix/sd-2199-virtualization-scroll-fallback
Mar 12, 2026
Merged

fix(painter-dom): skip non-scrollable scroll container in virtualization (SD-2199)#2383
harbournick merged 1 commit intomainfrom
fix/sd-2199-virtualization-scroll-fallback

Conversation

@tupizz
Copy link
Contributor

@tupizz tupizz commented Mar 12, 2026

Demo

CleanShot.2026-03-12.at.19.38.35.mp4

Summary

Fixes SD-2199: only the first 7 pages rendered on documents with 8+ pages when SuperDoc was embedded in certain layouts (version tester, manual testing site).

Root Cause

In v1.18.0 we added scroll container detection (#findScrollableAncestor in PresentationEditor, setScrollContainer on DomPainter) to fix virtualization offsets when SuperDoc is mounted inside a scrollable wrapper div.

The detection walks up the DOM tree and picks the first ancestor with overflow: auto or overflow: scroll CSS. updateVirtualWindow() then reads that element's scrollTop to compute which pages to render.

The problem: having overflow: auto in CSS does not mean the element actually scrolls. In flex layouts where the parent only has min-height (not height), the child grows to fit its content instead of constraining it. The element never scrolls, scrollTop stays at 0, and the virtual window is permanently stuck at pages 0-6 (window=5, overscan=1).

This is the exact layout in both affected environments:

.editorDock       display: flex; min-height: calc(100vh - 120px)  <-- NO height
  .vt-editor-root flex: 1; overflow: auto                        <-- grows to fit, never scrolls
    #superdoc

The local dev app works because .dev-app__layout has height: 100vh (fixed), so .dev-app__main is properly height-constrained and actually scrolls.

The Fix

Added a runtime check in updateVirtualWindow() that verifies the scroll container is actually scrollable (scrollHeight > clientHeight) before using its scrollTop. When the container has overflow: auto but isn't actually overflowing, the code falls through to the viewport-based getBoundingClientRect calculation, which works correctly with window-level scrolling.

// Before: trusted the scroll container blindly
} else if (this.scrollContainer) {

// After: verify content actually overflows the container
const scrollCont = this.scrollContainer;
const isScrollContainerActive = scrollCont != null
  && scrollCont.scrollHeight > scrollCont.clientHeight + 1;
} else if (isScrollContainerActive) {

Why this approach

Alternative considered: fix #findScrollableAncestor to check if the element is height-constrained. This is fragile because at setup time content may not be laid out yet, and determining whether a flex child will be constrained requires walking the entire ancestor chain analyzing flex/grid properties, explicit heights, and viewport units. The detection would need to be repeated on every layout change.

Why the runtime check is better: scrollHeight > clientHeight is the ground truth. It directly answers "is this element scrollable right now?" on every scroll event, with zero overhead (two property reads). It self-heals if the layout changes dynamically (e.g., window resize causes the container to become constrained). No assumptions about CSS layout models needed.

Test Plan

  • Added regression test: "falls through to viewport-based calculation when scroll container is not actually scrollable (SD-2199)"
  • All 784 painter-dom tests pass (including existing scroll container tests)
  • Manually verified on the version tester (labs.superdoc.dev layout) with a local build: all pages render correctly on 8+ page documents
  • Verify on the GCS manual testing site after publish
  • Verify on labs.superdoc.dev version tester after publish

…ion (SD-2199)

Only first 7 pages rendered on documents with 8+ pages when SuperDoc was
mounted inside an unconstrained flex layout. The scroll container detection
found an ancestor with overflow:auto CSS but that element was never actually
scrollable because its parent only had min-height (no height constraint).
The element grew to fit content instead of constraining it, so scrollTop
stayed at 0 and the virtual window never advanced beyond pages 0-6.

The fix adds a runtime check in updateVirtualWindow() that verifies the
scroll container is actually scrollable (scrollHeight > clientHeight)
before using its scrollTop. When the container is not scrollable, it falls
through to the viewport-based getBoundingClientRect calculation which works
correctly with window-level scrolling.
@linear
Copy link

linear bot commented Mar 12, 2026

Copy link
Collaborator

@harbournick harbournick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@harbournick harbournick merged commit 1e075f6 into main Mar 12, 2026
8 checks passed
@harbournick harbournick deleted the fix/sd-2199-virtualization-scroll-fallback branch March 12, 2026 23:30
@superdoc-bot
Copy link
Contributor

superdoc-bot bot commented Mar 12, 2026

🎉 This PR is included in superdoc v1.18.0-next.55

The release is available on GitHub release

@superdoc-bot
Copy link
Contributor

superdoc-bot bot commented Mar 12, 2026

🎉 This PR is included in superdoc-cli v0.2.0-next.130

The release is available on GitHub release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants