export interface AnimationCoordinatorOptions { duration?: number; easing?: string; } export interface CellPosition { left: number; top: number; width: number; height: number; } /** * FLIP-style animation coordinator for body cells with virtualization awareness. * * Triggered explicitly via {@link captureSnapshot} (before a layout-affecting * change) and {@link play} (after the renderer has placed cells at their new * positions). * * Three classes of cells participate in an animation: * - Persistent cells (visible before AND after): the same DOM node moves to * a new `top`/`left`; FLIP slides it from the old visual spot. * - Incoming cells (off-screen before, in DOM after): the renderer creates * them at their new position; if the snapshot has their pre-change * position (computed for ALL rows, not just the band), FLIP slides them * in from there. The portion that's outside the body's overflow clip is * never painted, so cells appear to slide in from the viewport edge. * - Outgoing cells (in DOM before, off-screen after): the renderer hands * them to {@link retainCell} along with their post-change off-screen * position; FLIP slides them out to that position, then removes them. */ export declare class AnimationCoordinator { private enabled; private duration; private easing; /** Pre-change positions for any cell we want to consider for animation. */ private snapshot; /** * One-shot synthetic origins for incoming cells that have no entry in the * captured snapshot (e.g. rows/columns that did not exist in the pre-render * state because they were inside a collapsed group). Used by accordion * animations so a newly-visible cell unfolds from its parent's position * rather than appearing in place. Cleared at the end of {@link play}. */ private incomingOrigins; private inFlight; /** Outgoing cells the renderer handed off; keyed per container so play() finds them. */ private retainedCells; private prefersReducedMotion; /** * Per-render cache of scroller layout metrics. Reading * `scrollHeight`/`clientHeight`/etc. after a style mutation forces a sync * layout flush; without this cache, scaleFlipDistance() forces a fresh * flush for every cell in the retain/play loops, turning a single sort * into hundreds of layout passes (observed: 513ms in `msRemove` for ~287 * cells, growing across consecutive sorts as DOM size grows). The cache * is cleared at the boundaries of a render cycle (captureSnapshot start * and play end / cancel) since column count and section heights are * stable within a single sort. */ private scrollerMetricsCache; constructor(opts?: AnimationCoordinatorOptions); setEnabled(enabled: boolean): void; setDuration(duration: number): void; setEasing(easing: string): void; isEnabled(): boolean; isInFlight(cellId: string): boolean; getDuration(): number; getEasing(): string; /** * Register synthetic pre-change origins for incoming cells that did not * exist in the captured snapshot. {@link play} consults this map before * giving up on a cell that has no `before` snapshot entry; matching cells * FLIP from the override origin to their final position. * * The map is consumed by the next `play()` call and cleared, so callers * must set it after `captureSnapshot` and before the render that creates * the corresponding cells. */ setIncomingOrigins(origins: Map | null): void; /** * Read scroller layout metrics for `container`, caching the result for the * remainder of the current render cycle. Subsequent calls in the same * cycle (e.g. for every cell in a retain or play loop) skip the DOM read, * which would otherwise force a synchronous layout flush after each style * mutation in the loop. */ private getScrollerMetrics; private clearScrollerMetricsCache; /** * Capture pre-change positions for cells we may want to animate. * * @param args.containers Body containers; rendered cells are read from the DOM. * @param args.preLayouts Optional per-container conceptual layout. Should * include positions for ALL rows in the dataset (not just the visible * band) so cells that newly enter the band can FLIP in from their actual * pre-change location and cells that leave the band can FLIP out to it. */ captureSnapshot(args: { containers: Array; preLayouts?: Map>; }): void; /** * The renderer asks before removing a cell whether the coordinator wants to * keep it for an out-animation. */ shouldRetain(cellId: string): boolean; /** * Whether the captured snapshot has an entry for the given cellId. The * accordion expand path uses this to detect "newly visible" cells (no * pre-change layout) so it can initialize them at zero size and let the * CSS transition grow them to full size. */ hasSnapshotEntry(cellId: string): boolean; /** * True when the snapshot has an entry for `cellId` AND the cell was * rendered in `currentContainer` at snapshot time. Returns false when the * cell came from a different container (cross-section pin/unpin) — its * snapshot position is in another container's coordinate frame, so a * FLIP applied locally would slide from a wrong visual origin and the * destination renderer should treat the cell as fresh (accordion grow * from 0 instead). * * Snapshot entries with `sourceContainer === null` (preLayouts / * conceptual positions) are treated as same-container so the existing * sort/reorder FLIP-from-off-screen behavior is preserved. */ hasSnapshotEntryInContainer(cellId: string, currentContainer: HTMLElement): boolean; /** * Hand a cell that the renderer would otherwise remove to the coordinator. * The coordinator updates its absolute positioning to the post-change layout * and will animate it from the snapshotted pre-change visual position to * that new position during {@link play}, then remove it from the DOM. * * The new position can be off-screen (e.g. the row sorted to a position * outside the visible band) — the body container's `overflow: hidden` * naturally clips the cell as it slides past the viewport edge. */ retainCell(args: { cellId: string; element: HTMLElement; container: HTMLElement; newPosition: CellPosition; }): void; /** * Take ownership of a retained (outgoing) ghost element so the renderer can * promote it back to a live cell — rather than tearing it down and creating * a fresh node — when its row becomes visible again. Returns the element * with its retained-only attributes/state stripped, or `null` if no ghost * is currently retained for this id in the container. * * Reusing the ghost preserves DOM continuity: the next play() step reads * the cell's mid-flight visual position from the snapshot (captured before * the render) and FLIPs it from there to its new live destination, so the * row glides instead of disappearing and a freshly created replacement * doesn't pop into existence at a clipped FLIP entry point. */ claimRetainedForReuse(cellId: string, container: HTMLElement): HTMLElement | null; /** * Hand off a cell that the renderer would otherwise remove for an accordion * shrink-out (column hide / pin-out from this section): the cell stays in * place and its size in the named axis is animated to zero by the * `.st-accordion-animating` CSS transition (width/height). Removed from the * DOM after the transition completes. * * Used when there is no destination position for the cell in the current * section's post-render layout — either because the column was hidden or * because it moved to a different pinned section. In the moved-section * case, the destination section creates a fresh cell that grows from zero * width via the existing accordion incoming-cell path, so the visual * effect is a synchronized shrink-here / grow-there pair rather than a * cross-container slide (which would require translating coordinates * between two different container coordinate frames). */ shrinkOutCell(args: { cellId: string; element: HTMLElement; container: HTMLElement; axis: "horizontal" | "vertical"; }): void; /** * Discard any retained cell with this id in the given container. Called by * the renderer when it's about to create a fresh cell with the same id, so * we don't have two DOM nodes claiming the same logical slot. */ discardRetainedIfPresent(cellId: string, container: HTMLElement): void; /** * Apply the FLIP invert + play step to every cell present in the snapshot * that is now in the DOM (either as an actively rendered cell or as a * retained cell). Clears the snapshot. */ play(args: { containers: Array; }): void; /** * Cancel every in-flight transition and clear any armed snapshot. Active * cells snap to their final positions; retained cells are removed from the * DOM so we don't leak nodes. */ cancel(): void; destroy(): void; private readPosition; private startTransition; private cancelInFlight; private finalizeCell; private finishElement; private isCellRetained; }