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.
measurePages(editor, options) reads one coherent editor snapshot and returns an immutable PageLayoutSnapshot. It neither subscribes nor publishes. A custom PageLayoutEngine receives detached immutable input and must conserve source ranges, fragment order, paths, and declared non-text sizes.
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.
Pass a named root to PagedEditable or measurePages to measure and render that root. Omit root for the primary document. "main" is an internal root spelling and is rejected at the public boundary.
page accepts a PageSettings value or a defineStateField field, so the app keeps shared page settings in editor state without storing derived geometry. Persist that field with persist: { ...pageSettingsCodec, version: 1 }, which stores validated finite settings; the built-in presets are "a4" and "letter".
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.