editor: size the note without rendering pages it will scroll past

This commit is contained in:
Ammar Ahmed
2026-08-27 14:05:47 +05:00
parent 297ea7bc16
commit 537231a901
3 changed files with 43 additions and 5 deletions

View File

@@ -195,6 +195,26 @@ describe("scroll anchor", () => {
editor.destroy();
});
test("does not render pages it is about to scroll away from", () => {
const editor = createEditor(createContainer());
stubLayout(editor);
const before = Array.from(editor.view.dom.children).map((page) =>
(page as HTMLElement).hasAttribute("data-page-placeholder")
);
restoreScrollAnchor(editor.view, { blockId: "blk300", offset: 0 });
// page 3 is neither an edge, nor next to the caret, nor near the anchor:
// nothing should have rendered it on the way past
expect(before[3]).toBe(true);
expect(
(editor.view.dom.children[3] as HTMLElement).hasAttribute(
"data-page-placeholder"
)
).toBe(true);
editor.destroy();
});
test("reports failure for a block that is no longer there", () => {
const editor = createEditor(createContainer());
stubLayout(editor);

View File

@@ -22,6 +22,7 @@ import { EditorView } from "@tiptap/pm/view";
import { profiler } from "../../utils/profiler.js";
import { isPage } from "./split.js";
import {
calibrateHeightsNow,
findScrollParent,
renderViewportNow,
viewportKey
@@ -113,11 +114,12 @@ export function restoreScrollAnchor(
return false;
}
// Empty pages start out sized from a guess, and the note shrinks once the
// first real measurements arrive. Doing that first means scrolling against
// the heights the note will actually have, instead of being dragged along
// when it changes size a moment later.
renderViewportNow(view);
// Empty pages start out sized from a guess, and the note changes height once
// the first real measurements arrive. Sizing them first means scrolling
// against the heights the note will actually have, and doing it without
// rendering anything avoids showing pages here that are about to be left
// behind.
calibrateHeightsNow(view);
const target = findBlock(view.state.doc, anchor.blockId);
if (!target.found) {

View File

@@ -38,6 +38,16 @@ type PageRange = { from: number; to: number; index: number };
const EMPTY_VISIBLE: Set<string> = new Set();
const pending = new WeakMap<EditorView, () => void>();
const calibrations = new WeakMap<EditorView, () => void>();
/**
* Sizes the empty pages from what the note actually looks like, without
* changing which pages are rendered. Used before scrolling somewhere, so the
* note is not still changing height once it gets there.
*/
export function calibrateHeightsNow(view: EditorView): void {
calibrations.get(view)?.();
}
/**
* Works out what is on screen right now instead of waiting for the next frame.
@@ -500,6 +510,11 @@ export function viewportPlugin(heights: HeightMap): Plugin<ViewportState> {
};
pending.set(editorView, flush);
calibrations.set(editorView, () => {
updateMetrics();
measureRenderedPages();
resizePlaceholders();
});
window.addEventListener("beforeprint", beforePrint);
window.addEventListener("afterprint", afterPrint);
@@ -533,6 +548,7 @@ export function viewportPlugin(heights: HeightMap): Plugin<ViewportState> {
window.removeEventListener("afterprint", afterPrint);
printMedia?.removeEventListener?.("change", onPrintMedia);
pending.delete(editorView);
calibrations.delete(editorView);
window.removeEventListener("resize", schedule);
scrollParent?.removeEventListener("scroll", schedule);
layout?.disconnect();