import { createPretextPageLayoutEngine } from 'platejs/pagination';
import {
PagedEditable,
usePageLayout,
} from 'platejs/pagination/react';
import { Plate, useCreateEditor } from 'platejs/react';
import { useMemo, useRef } from 'react';
export function DocumentEditor() {
const editor = useCreateEditor({ initialValue: value });
const editableRef = useRef<HTMLDivElement>(null);
const engine = useMemo(() => createPretextPageLayoutEngine(), []);
const layout = usePageLayout(editableRef);
return (
<Plate editor={editor}>
<output>{layout?.pages.length ?? 0} pages</output>
<PagedEditable
ref={editableRef}
engine={engine}
page={{ margins: 72, preset: 'letter' }}
pageView={{ gap: 24, mode: 'single' }}
renderPage={({ attributes, page }) => (
<div {...attributes} aria-label={`Page ${page.index + 1}`} />
)}
/>
</Plate>
);
}renderPage receives page attributes and geometry. It renders page chrome;
PagedEditable places the editable content on the same canvas. Spread mode
changes page arrangement without changing the document.
usePageLayout reads the layout published by the exact editable element in
its ref. This keeps sibling paged editors independent and returns null before
the surface publishes its first valid layout.
Text blocks use inline flow by default. Images, embeds, tables, and other non-inline structures must describe how they fragment:
<PagedEditable
page={{ margins: 72, preset: 'letter' }}
fragmentation={({ element }) => {
if (element.type === 'image') {
return { type: 'atomic', size: { height: 320, width: 480 } };
}
if (element.type === 'table') {
return {
type: 'direct-children',
sizes: element.children.map(() => ({ height: 36, width: 624 })),
};
}
if (element.type === 'code_block') {
return { type: 'text', keepTogether: true };
}
}}
/><PagedEditable
page={{ margins: 72, preset: 'letter' }}
fragmentation={({ element }) => {
if (element.type === 'image') {
return { type: 'atomic', size: { height: 320, width: 480 } };
}
if (element.type === 'table') {
return {
type: 'direct-children',
sizes: element.children.map(() => ({ height: 36, width: 624 })),
};
}
if (element.type ===
atomic places one indivisible box. direct-children lets pagination place
each immediate child while the document tree stays intact. text can request
soft keep-together behavior; content taller than a page still fragments.
Use usePageLayoutFragments() inside an element renderer to read the current
element's mounted page placements. The hook derives the element path from the
renderer context.
createPretextPageLayoutEngine() measures rich inline text with browser font
metrics. Supply matching typography callbacks when rendered fonts, line
heights, or block spacing differ from the defaults. Call measurePages with an
explicit engine for headless snapshots. createEstimatedPageLayoutEngine() is
useful where browser text measurement is unavailable.
Different font files and browser profiles can produce different line breaks. Derived pages are therefore a view result, not stored page-break authority. Products that require identical exported pages need one controlled measurement environment for that export.
PagedEditable mounts every page by default. Set virtualize only after the
complete paged DOM exceeds the product's tested interaction or memory budget.
Virtualization keeps selected and promoted content mounted, but native browser
find, accessibility traversal, DOM printing, and third-party DOM integrations
see mounted content only. Use model-based export for the complete document.