import type { VisibleRange } from './chatTypes.js'; /** * Reactive height cache for chat messages, backed by a prefix-sum array. * * Heights are stored in a plain object keyed by message ID; a parallel * prefix-sum array indexed by message position lets total/offset/range * queries stay O(1) and O(log n) at any list size. The cache is kept in * sync with the messages array via `sync(messages, getMessageId, est)`, * which the public functions below call before each query. * * A version counter triggers Svelte derivations whenever heights change. */ export declare class ChatHeightCache { #private; /** Get the measured height for a message, or undefined if not yet measured. */ get(id: string): number | undefined; /** * Set the measured height for a message. Returns true if the value changed. * * The version bump is deferred to a microtask so a batch of `set()` calls * within one task (the typical ResizeObserver burst) coalesces into a * single bump and one downstream reactive cascade. The boolean return is * still synchronous and accurate per-call — only the cascade is deferred. */ set(id: string, height: number): boolean; /** Remove a message from the cache (e.g. when messages are pruned). */ delete(id: string): void; /** Check if a message has been measured. */ has(id: string): boolean; /** Number of measured entries. */ get size(): number; /** Current version — use this to establish reactive dependencies on any height change. */ get version(): number; /** Clear all cached heights. */ clear(): void; /** * Reconcile internal position state with a new messages array. * * Fast paths: * - No-op when the array reference is unchanged AND length matches. * - Append: tail-extends `#orderedIds` and `#prefixSum` in place. * - Prepend: rebuilds order and marks the prefix sum dirty from 0. * Otherwise: full rebuild + dirty=0. */ sync(messages: readonly T[], getMessageId: (_message: T) => string, estimatedHeight: number): void; /** Total content height — O(1) after dirty flush. */ getTotalHeight(): number; /** Pixel offset of message at `index` from the start of the messages section — O(1). */ getOffsetForIndex(index: number): number; /** * Smallest index `i` whose message overlaps the viewport from below * (i.e. `prefixSum[i+1] > viewTop`). Returns -1 when every message is * above `viewTop` (viewport scrolled past the end). */ findVisibleStart(viewTop: number): number; /** * Largest index `i` whose top sits above `viewBottom` * (i.e. `prefixSum[i] < viewBottom`). Returns -1 when every message is * at or below `viewBottom` (viewport scrolled past the start). */ findVisibleEnd(viewBottom: number): number; } /** * Calculate the total content height given messages and a height cache. * * @param messages - Array of message objects * @param getMessageId - Function to extract a unique ID from a message * @param heightCache - The reactive height cache instance * @param estimatedHeight - Fallback height in pixels for unmeasured messages * @returns Total content height in pixels */ export declare const calculateTotalHeight: (messages: T[], getMessageId: (_message: T) => string, heightCache: ChatHeightCache, estimatedHeight: number) => number; /** * Calculate the Y offset for a message at a given index. * * @param messages - Array of message objects * @param index - Target index to calculate offset for * @param getMessageId - Function to extract a unique ID from a message * @param heightCache - The reactive height cache instance * @param estimatedHeight - Fallback height in pixels for unmeasured messages * @returns Pixel offset from the top of the content area */ export declare const calculateOffsetForIndex: (messages: T[], index: number, getMessageId: (_message: T) => string, heightCache: ChatHeightCache, estimatedHeight: number) => number; /** * Arguments for `calculateVisibleRange`. * * `totalHeight` is taken as an input so the caller can reuse the value it * already derives elsewhere; recomputing it here would walk the message * list a second time on every reactive update. */ export interface CalculateVisibleRangeArgs { messages: T[]; getMessageId: (_message: T) => string; heightCache: ChatHeightCache; estimatedHeight: number; totalHeight: number; scrollTop: number; viewportHeight: number; headerHeight: number; footerHeight: number; overscan: number; } /** * Calculate the visible message range for a virtual chat viewport. * * Translates `scrollTop` from content-local coordinates (header + messages * + footer) into messages-local coordinates, then binary-searches the * cached prefix sum to locate the first/last messages that overlap the * viewport. Adds `overscan` items on each side to keep DOM stable while * scrolling. * * Layout assumed by this function (matches `SvelteVirtualChat.svelte`): * * ┌─ scroll container (scrollTop) ───────────┐ * │ optional empty space (topGap, when │ * │ content fits and bottom-gravity pushes │ * │ everything down) │ * │ ── header (headerHeight) ── │ * │ ── messages container (totalHeight) ── │ * │ ── footer (footerHeight) ── │ * └──────────────────────────────────────────┘ * * The fix vs a naïve implementation: subtract `headerHeight` (and `topGap`) * from `scrollTop` so the search runs in the same coordinate space as the * cached prefix sum, and anchor the fallback to `messages.length - 1` when * the viewport has scrolled past every message (e.g. into a tall footer). */ export declare const calculateVisibleRange: (args: CalculateVisibleRangeArgs) => VisibleRange;