/** * Pure geometry helpers for the managed history viewport. * * The component owns one absolute viewport-top row for wheel, page, and * scrollbar input. These helpers translate that coordinate into a temporary * {@link ScrollAnchor}: the render-group containing the row plus the number * of rows clipped inside it. Rendering begins at that anchor, with no spacer * elements. * * `null` means "pinned to the newest output" (follow mode). Pinned rendering * uses `justifyContent:'flex-end'` and needs no position math at all. * * Prefix sums over the {@link EntryHeightCache} translate absolute rows to * mounted groups and size the scrollbar. When a measurement corrects those * sums, the component resolves the same absolute row again instead of letting * separate anchor/offset/thumb states drift apart. * * Pure TypeScript — no React, no Ink. Unit-testable without mounting anything. */ import type { EntryHeightCache } from './height-cache.js'; /** Scroll position of the managed viewport. */ export interface ScrollAnchor { /** Index (into the render-group array) of the group at the viewport top. */ index: number; /** Rows of that group clipped above the viewport top. Always >= 0 and less * than the group's height (self-heals via clamping when heights change). */ clip: number; } /** Everything the pure scroll math needs to know about current geometry. */ export interface ScrollGeometry { /** Height cache whose prefix sums cover exactly the render groups. */ cache: EntryHeightCache; /** Number of render groups (must equal the cache's synced id count). */ groupCount: number; /** Viewport height in rows (>= 1). */ viewportRows: number; /** Fixed-height live tail rendered after the last group (0 when absent). */ tailRows: number; } /** Extra rows mounted beyond the visible viewport so rapid scrolling has * content ready before the next measurement pass. */ export declare const OVERSCAN_ROWS = 8; /** Total scrollable content height in rows (groups + live tail). */ export declare function contentRows(geometry: ScrollGeometry): number; /** Highest legal viewport-top row. 0 when everything fits in the viewport. */ export declare function maxTopRow(geometry: ScrollGeometry): number; /** * Absolute content row of the viewport top implied by an anchor, clamped to * the legal range. `null` (pinned) maps to {@link maxTopRow}. */ export declare function anchorTopRow(geometry: ScrollGeometry, anchor: ScrollAnchor | null): number; /** * Anchor for an absolute viewport-top row. Returns `null` (pinned) when the * row is at or past the bottom-most position. The returned anchor always * points inside a real group: a top row landing inside the tail suffix is * clamped back onto the last group. */ export declare function anchorAtTopRow(geometry: ScrollGeometry, topRow: number): ScrollAnchor | null; /** * Move the viewport by `deltaUp` rows (positive scrolls toward older content, * negative toward newer). From pinned, positive deltas un-pin; reaching the * bottom returns `null` (re-pin). */ export declare function scrollAnchorBy(geometry: ScrollGeometry, current: ScrollAnchor | null, deltaUp: number): ScrollAnchor | null; /** Anchor for the very top of the transcript (or `null` when it all fits). */ export declare function scrollAnchorToTop(geometry: ScrollGeometry): ScrollAnchor | null; /** Rows moved by one PageUp/PageDown press. */ export declare function pageRows(viewportRows: number): number; /** * Anchor for a 0-based cell clicked on a scrollbar track of `viewportRows` * height: cell 0 → oldest content, the last cell → pinned to newest. */ export declare function anchorForTrackCell(geometry: ScrollGeometry, cell: number): ScrollAnchor | null; /** Result of planning which groups to mount for one frame. */ export interface MountPlan { /** First group index to mount (inclusive). */ startIdx: number; /** Last group index to mount (exclusive). */ endIdx: number; /** Whether the live tail should be mounted after the groups. */ mountTail: boolean; } /** * Plan the mounted window for a scrolled viewport: from the anchor downward * until the (estimated or measured) heights cover the anchor clip, the * viewport, the overscan, and `extraRows` of underfill correction. Estimates * only ever decide HOW MANY groups to mount — mounting too many is harmless * (the viewport clips them), and mounting too few is corrected by the * measurement pass bumping `extraRows`. */ export declare function planFromAnchor(geometry: ScrollGeometry, anchor: ScrollAnchor, extraRows?: number): MountPlan; /** * Plan the mounted window for the pinned viewport: from the bottom upward * until the heights cover the viewport (minus the always-mounted tail) plus * overscan and correction. Overfill is clipped from the top by * `justifyContent:'flex-end'`, so generosity is safe. */ export declare function planPinned(geometry: ScrollGeometry, extraRows?: number): MountPlan; //# sourceMappingURL=scroll-anchor.d.ts.map