All files / roosterjs-content-model-dom/lib/domUtils scrollRectIntoView.ts

88.24% Statements 30/34
77.27% Branches 17/22
100% Functions 4/4
88.24% Lines 30/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84                      1x         94x 106x     55x   55x 7x   7x                 55x 55x 55x     55x 21x 21x     55x 18x 18x       55x 55x   55x   7x 4x   3x   48x             48x 15x 33x 17x             46x 40x   6x      
import type { DOMHelper, Rect } from 'roosterjs-content-model-types';
 
/**
 * Scroll a given rectangle into view within a scroll container
 * @param scrollContainer The container to scroll
 * @param visibleRect The currently visible rectangle within the scroll container
 * @param domHelper The DOM helper of the editor
 * @param targetRect The target rectangle to scroll into view
 * @param scrollMargin Optional margin to apply when scrolling
 * @param preferTop Optional flag to indicate whether to prefer aligning the top or bottom of the target rect when the target rect is higher than visible rect @default false
 */
export function scrollRectIntoView(
    scrollContainer: HTMLElement,
    visibleRect: Rect,
    domHelper: DOMHelper,
    targetRect: Rect,
    scrollMargin: number = 0,
    preferTop: boolean = false
) {
    let zoomScale: number | undefined;
    let margin = 0;
 
    if (scrollMargin != 0) {
        zoomScale = getZoomScale(domHelper, zoomScale);
 
        margin = Math.max(
            0,
            Math.min(
                scrollMargin * zoomScale,
                (visibleRect.bottom - visibleRect.top - targetRect.bottom + targetRect.top) / 2
            )
        );
    }
 
    const top = targetRect.top - margin;
    const bottom = targetRect.bottom + margin;
    const height = bottom - top;
 
    // Define scroll operations
    const scrollUp = () => {
        zoomScale = getZoomScale(domHelper, zoomScale);
        scrollContainer.scrollTop -= (visibleRect.top - top) / zoomScale;
    };
 
    const scrollDown = () => {
        zoomScale = getZoomScale(domHelper, zoomScale);
        scrollContainer.scrollTop += (bottom - visibleRect.bottom) / zoomScale;
    };
 
    // Determine which operations to perform and in what order
    const needsScrollUp = top < visibleRect.top;
    const needsScrollDown = bottom > visibleRect.bottom;
 
    if (height > visibleRect.bottom - visibleRect.top) {
        // If the target rect is larger than visible rect, only perform one scroll operation
        if (preferTop) {
            scrollUp();
        } else {
            scrollDown();
        }
    } else Iif (preferTop) {
        if (needsScrollUp) {
            scrollUp();
        } else if (needsScrollDown) {
            scrollDown();
        }
    } else {
        if (needsScrollDown) {
            scrollDown();
        } else if (needsScrollUp) {
            scrollUp();
        }
    }
}
 
// domHelper.calculateZoomScale() may be an expensive call, so we cache the value during a single operation
function getZoomScale(domHelper: DOMHelper, knownZoomScale: number | undefined): number {
    if (knownZoomScale === undefined) {
        return domHelper.calculateZoomScale();
    } else {
        return knownZoomScale;
    }
}