Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/fuzzy-pans-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@react-pdf/layout": minor
"@react-pdf/renderer": minor
"@react-pdf/types": minor
---

feat: add `breakWhenNeeded` for wrap-able nodes that should move to the next page before their first split
9 changes: 9 additions & 0 deletions packages/layout/src/node/shouldBreak.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import isFixed from './isFixed';
const getBreak = (node: SafeNode) =>
'break' in node.props ? node.props.break : false;

const getBreakWhenNeeded = (node: SafeNode) =>
'breakWhenNeeded' in node.props ? node.props.breakWhenNeeded : false;

const getMinPresenceAhead = (node: SafeNode) =>
'minPresenceAhead' in node.props ? node.props.minPresenceAhead : 0;

Expand Down Expand Up @@ -53,10 +56,16 @@ const shouldBreak = (
// (as long as react-pdf does not support breaking into differently sized containers)
const breakingImprovesPresence =
previousElements.filter((node: SafeNode) => !isFixed(node)).length > 0;
const shouldBreakWhenNeeded =
shouldSplit &&
canWrap &&
getBreakWhenNeeded(child) &&
breakingImprovesPresence;

return (
getBreak(child) ||
(shouldSplit && !canWrap) ||
shouldBreakWhenNeeded ||
(!shouldSplit && endOfPresence > height && breakingImprovesPresence)
);
};
Expand Down
6 changes: 6 additions & 0 deletions packages/layout/src/types/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export type NodeProps = {
* @see https://react-pdf.org/advanced#page-breaks
*/
break?: boolean;
/**
* Move the element to the next page when it would otherwise start splitting
* in the remaining space, while still allowing it to continue across later
* pages.
*/
breakWhenNeeded?: boolean;
/**
* Hint that no page wrapping should occur between all sibling elements following the element within n points
* @see https://react-pdf.org/advanced#orphan-&-widow-protection
Expand Down
63 changes: 63 additions & 0 deletions packages/layout/tests/node/shouldBreak.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,69 @@ describe('node shouldBreak', () => {
expect(result).toEqual(true);
});

test('should break when breakWhenNeeded is enabled and moving improves presence', () => {
const result = shouldBreak(
{
type: 'VIEW',
props: { wrap: true, breakWhenNeeded: true },
style: {},
children: [],
box: {
top: 700,
right: 0,
bottom: 0,
left: 0,
height: 400,
width: 200,
},
},
[],
1000,
[
{
type: 'VIEW',
props: {},
style: {},
children: [],
box: {
top: 0,
right: 0,
bottom: 0,
left: 0,
height: 700,
width: 200,
},
},
],
);

expect(result).toEqual(true);
});

test('should not break when breakWhenNeeded is enabled but the node is already first on the page', () => {
const result = shouldBreak(
{
type: 'VIEW',
props: { wrap: true, breakWhenNeeded: true },
style: {},
children: [],
box: {
top: 700,
right: 0,
bottom: 0,
left: 0,
height: 400,
width: 200,
},
},
[],
1000,
[],
);

expect(result).toEqual(false);
});

test('should break when minPresenceAhead is large enough and there are overflowing siblings after the child', () => {
const result = shouldBreak(
{
Expand Down
77 changes: 77 additions & 0 deletions packages/layout/tests/steps/resolvePagination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,83 @@ describe('pagination step', () => {
expect(page2.children![0].box!.height).toBe(40);
});

test('should move breakWhenNeeded containers to the next page before splitting them', async () => {
const yoga = await loadYoga();

const layout = calcLayout({
type: 'DOCUMENT',
yoga,
props: {},
children: [
{
type: 'PAGE',
props: {},
style: {
width: 5,
height: 60,
},
children: [
{
type: 'VIEW',
style: {
width: 5,
height: 20,
},
props: {},
children: [],
},
{
type: 'VIEW',
style: {
width: 5,
},
props: {
breakWhenNeeded: true,
},
children: [
{
type: 'VIEW',
style: {
height: 30,
},
props: {},
children: [],
},
{
type: 'VIEW',
style: {
height: 30,
},
props: {},
children: [],
},
{
type: 'VIEW',
style: {
height: 30,
},
props: {},
children: [],
},
],
},
],
},
],
});

const page1 = layout.children[0];
const page2 = layout.children[1];
const page3 = layout.children[2];

expect(layout.children).toHaveLength(3);
expect(page1.children).toHaveLength(1);
expect(page2.children).toHaveLength(1);
expect(page3.children).toHaveLength(1);
expect(page2.children![0].children).toHaveLength(2);
expect(page3.children![0].children).toHaveLength(1);
});

test('should not infinitely loop when splitting pages', async () => {
const yoga = await loadYoga();

Expand Down
6 changes: 6 additions & 0 deletions packages/renderer/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ declare namespace ReactPDF {
* @see https://react-pdf.org/advanced#page-breaks
*/
break?: boolean;
/**
* Move the element to the next page when it would otherwise start
* splitting in the remaining space, while still allowing it to continue
* across later pages.
*/
breakWhenNeeded?: boolean;
/**
* Hint that no page wrapping should occur between all sibling elements following the element within n points
* @see https://react-pdf.org/advanced#orphan-&-widow-protection
Expand Down
1 change: 1 addition & 0 deletions packages/types/node.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface BaseProps {
id?: string;
fixed?: boolean;
break?: boolean;
breakWhenNeeded?: boolean;
debug?: boolean;
bookmark?: Bookmark;
minPresenceAhead?: number;
Expand Down