Skip to content

fix(layout): column splits ignore minPresenceAhead windows - #3580

Closed
exoego wants to merge 1 commit into
diegomura:masterfrom
exoego:chore/pasted-content-id-15a8-react
Closed

exoego wants to merge 1 commit into
diegomura:masterfrom
exoego:chore/pasted-content-id-15a8-react

Conversation

@exoego

@exoego exoego commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Problem

With the new engine (experimentalPagination or a page layout), a node with minPresenceAhead can be left at the bottom of a page with only a line or two after it. This happens when the next sibling is a container that stacks its children vertically, like a table made of View rows. The engine breaks the page inside that container.

The legacy engine moves the node to the next page instead. #3504 promised the same thing for the new engine. No page break may fall inside the window after the node. If the window does not fit, the node moves to the next page.

Repro

<Document>
  <Page size="A6" style={{ padding: 20 }} experimentalPagination>
    <View style={{ height: 330 }} />
    <Text minPresenceAhead={60}>Heading</Text>
    <View>
      {Array.from({ length: 20 }, (_, i) => (
        <Text key={i}>{`row ${i + 1}`}</Text>
      ))}
    </View>
  </Page>
</Document>
Case Page 1 Page 2
Legacy engine (no heading) Heading, row 1, …
New engine Heading, row 1 row 2, …
New engine, rows not wrapped in a View (no heading) Heading, row 1, …
New engine, with this fix (no heading) Heading, row 1, …

Cause

#3504 checks the window at three places. These are break candidates, leaf splits, and row slices. A split inside a column is not checked.

fit/row.ts declines a row slice inside a window. fit/column.ts has no such check. The column's children are filled by a separate fill that does not know about the outer window. So the outer fill accepts the split without checking the window.

Fix

Add the same check from fit/row.ts to fit/column.ts. A column split is also a break at the page bottom, so it is not allowed inside a window. When the column declines, tryOverflow rewinds to the last break candidate. No candidate is recorded inside the window, so it rewinds to before the node. The node and the column then move to the next page together.

Tests

  • paginate.test.ts has a new case in minPresenceAhead windows where the item after the window is a column. It also takes an image snapshot (min-presence-column-no-split.png).
  • parity.test.ts has a new case in minPresenceAhead where the next sibling is a View with children. It checks that the new engine matches the legacy engine.

Both tests fail without the fix and pass with it.

I also checked a real 273-page report full of tables. Without the fix, the new engine made 270 pages and started to differ from the legacy engine at page 13. With the fix, it made 273 pages, and every page matched the legacy output pixel for pixel.

A column could split inside the window after a minPresenceAhead node,
leaving the node at the page bottom with a line or two after it. Decline
the split like a row slice, so the node moves to the next page together
with the column.
@vercel

vercel Bot commented Oct 6, 2026

Copy link
Copy Markdown

@exoego is attempting to deploy a commit to the diegomura Team on Vercel.

A member of the Team first needs to authorize it.

@exoego exoego closed this Oct 6, 2026
@exoego
exoego deleted the chore/pasted-content-id-15a8-react branch October 6, 2026 05:19
@exoego

exoego commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Sorry, will recreate

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant