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         108x 120x     62x   62x 7x   7x                 62x 62x 62x     62x 23x 23x     62x 22x 22x       62x 62x   62x   7x 4x   3x   55x             55x 19x 36x 19x             52x 46x   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;
    }
}