/** A row's identity and vertical position used for animation bookkeeping. */ export interface RowPosition { nodeId: string; top: number; } /** * The per-panel DOM parts of one rendered row, as held by `BodyRenderer`'s * render cache. Passed to {@link RowAnimator.animate} directly so the animator * never has to re-query the DOM for elements the renderer already tracks. */ export interface RowPanelParts { left: HTMLElement | null; center: HTMLElement | null; right: HTMLElement | null; } /** The scroll window the animation is judged against, in content coordinates. */ export interface AnimationViewport { /** Current vertical scroll offset of the body. */ scrollTop: number; /** Visible height of the body viewport. */ height: number; } /** * Distinguishes the pipeline that caused the row change so the animator can * choose the appropriate duration and entrance style. * * - `'sort'` – rows reorder; all existing rows slide to new positions. * - `'filter'` – rows appear / disappear; shifted rows slide, new rows fade in. * - `'detail'` – a Master/Detail row expanded/collapsed; shares filter's * slide/fade-in behaviour but runs slightly faster — deliberately distinct * from `'filter'` so tuning quick-filter typing feedback never affects the * detail-row expand/collapse feel, and vice versa. * - `'group'` – a row-group or tree node expanded/collapsed. Like `'filter'` * (carried-over rows FLIP, new child rows fade in) but it is a *localized* * insert/remove, never a page swap — so it is exempt from the page-replace * fallback that would otherwise slide the whole viewport (see * {@link PAGE_REPLACE_SHARE_THRESHOLD}). Expanding a large group makes most * of the virtualised window "new", which would trip that heuristic and jerk * the entire body; anchoring the carried-over rows keeps the toggle steady. */ export type RowAnimationType = 'sort' | 'filter' | 'detail' | 'group'; /** * FLIP-based row animation engine. * * ### Usage * ``` * // 1. Before the data pipeline: * animator.capture(currentVisibleRows, 'sort'); * * // 2. After the DOM has been updated with the new row layout: * animator.animate(renderedRowParts, newVisibleRows, viewport); * ``` * * ### Why FLIP, and why transforms * Rows are positioned by `top` (written into a single stylesheet by * `RowPositionSheet`), which is a layout property and cannot be animated * cheaply — transitioning `top` on hundreds of absolutely-positioned rows * forces layout every frame. So the new `top` is applied instantly and the * *visual* movement is played back on `transform`, which the compositor can run * without touching layout or paint. `translate3d` is used rather than * `translateY` so each animating row is promoted to its own GPU layer for the * duration. * * ### Ordering * The pipeline is strictly: * capture old positions → sort → render (DOM now shows the new order) → * read new positions → compute delta → invert via transform → one forced style * flush → `requestAnimationFrame` → play. `capture()` must be called *before* * the data pipeline runs; `animate()` *after* the renderer has committed the * new layout. * * ### Sort animation * Every row that existed before and after the sort, and whose movement is * visible, slides from its old vertical position to its new one (classic FLIP). * * ### Filter animation * - **Rows that moved** (still visible but at a different `top`): FLIP slide. * - **New rows** (not in the pre-filter snapshot): fade in from a small upward * offset — identical to AG Grid's filter entrance. * - **Removed rows**: already gone from the DOM; no exit animation is attempted * (matching AG Grid community behaviour). * * ### Virtualization * Only rows the renderer currently holds DOM for are considered, and of those * only rows whose movement intersects the viewport actually animate — see * {@link VIEWPORT_SLACK_FACTOR}. Sorting one million rows therefore animates * the same ~30 elements as sorting one hundred. * * ### Paginated data * `capture()`/`animate()` compare only the rows of the **current page** (the * pipeline paginates before laying out `visibleRows`). When a sort or filter * swaps most of the page's rows for rows from other pages, a per-row FLIP would * be meaningless, so the whole page instead plays one uniform transform-only * slide entrance — opacity is left untouched so the content never blinks. See * {@link PAGE_REPLACE_SHARE_THRESHOLD}. */ export declare class RowAnimator { private snapshot; private animationType; /** * Elements carrying animation styles right now, with the `transitionend` * listener attached to each. Cleared eagerly at the start of the next * `animate()` and on `destroy()`, so a transition that never fires its end * event (element detached mid-flight) can still not leak `will-change`. */ private readonly active; /** Elements inverted for the next frame but not yet transitioning. */ private readonly pending; /** The scheduled play frame, if the animator is currently between FLIP phases. */ private playFrame; /** * `true` between the invert phase and the frame that starts the transitions. * * `active` is only populated once the play phase runs, so without this flag * the animator would report itself idle for one frame in the middle of a * sequence it is very much in the middle of. */ private playPending; /** Scratch buffers, reused across runs so a sort allocates no per-row arrays. */ private readonly toFlip; private readonly flipDeltas; private readonly toFadeIn; private readonly toSlideIn; /** * Snapshot current row positions **before** a pipeline run so the animator * has a reference frame for the FLIP calculation. * * @param rows - Current visible rows (nodeId + top). * @param type - Controls duration and entrance style. */ capture(rows: ReadonlyArray, type?: RowAnimationType): void; /** * Apply animations after the DOM has been updated with the new row order. * * @param rendered - The renderer's live cache of row `nodeId` → panel elements. * Iterated directly; never queried from the DOM. * @param newRows - Newly rendered rows with updated `top` values. * @param viewport - Scroll window used to skip rows whose movement is * off-screen. Omit to animate every carried-over row. */ animate(rendered: ReadonlyMap, newRows: ReadonlyArray, viewport?: AnimationViewport): void; /** * Strips animation styling from `el` once its transform transition ends, so * later renders and style recalculations are not impeded by a lingering * `will-change`. Uses `transitionend` rather than a timer: the element is * released the instant the compositor is actually finished, with no guessing * and no drift between the CSS duration and a JS timeout. */ private trackUntilDone; /** Removes all animation styling and the end listener from one element. */ private release; /** * Finalises every in-flight element immediately. Guarantees no element is * left with a stale transform or an orphaned `will-change` when a new * animation starts or the grid is torn down — the safety net that lets this * class avoid timer-based cleanup entirely. */ private finishAll; /** Returns `true` when a `capture()` is pending and the next `animate()` will run. */ hasPending(): boolean; /** * Returns `true` while rows are mid-transition. * * Lets a caller driving repeated re-layouts — a real-time feed reordering a * sorted column — skip capturing a new snapshot until the current slide has * landed. Without that check, a feed ticking faster than the animation * duration restarts the FLIP on every batch, and rows never finish travelling: * the effect reads as a permanent smear rather than as motion. Skipping is * safe because the rows still move to their correct positions; only the * animation for that intermediate step is dropped. */ isAnimating(): boolean; destroy(): void; } //# sourceMappingURL=row-animator.d.ts.map