Viewport-driven subtree-content windowing for ProseMirror. The whole
document stays in EditorState; container nodes away from
the viewport keep a single height-preserving placeholder.
Benchmark
Both editors below hold the same generated document and run in this tab. Only the DOM differs. Picking a size re-runs the comparison.
Generating document…
| Mode | DOM elements | Mount + first paint |
|---|---|---|
| Windowed | — | — |
| Full DOM | — | — |
| Reduction | — | |
—
Usage
Add it as an ordinary plugin. Pick block container node types whose
renderer exposes a contentDOM.
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { createSubtreeWindowing } from "prosemirror-subtree-window";
const windowing = createSubtreeWindowing({
scrollRoot: editorHost,
nodeTypes: ["section"],
minDocumentUnits: 2_000,
});
const state = EditorState.create({
doc,
plugins: [...plugins, windowing.plugin],
});
const view = new EditorView(editorHost, { state });
Window moves are decoration-only transactions, so they add no steps and never enter history. To move the selection into a node that is not materialized yet, dispatch through the plugin:
windowing.setSelection(view, TextSelection.create(doc, pos), {
scrollIntoView: true,
focus: true,
});
Plain tr.setSelection(...) works as well — both endpoint
paths of a selection are materialized automatically.
Full option reference.