All files scroll.ts

46.66% Statements 28/60
37.5% Branches 3/8
60% Functions 3/5
46.66% Lines 28/60

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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                                                        2x 15x 15x 15x                                   2x 15x                         15x 15x 15x           15x     2x   2x     2x 15x 15x 15x 15x 15x   15x 15x   15x 15x 15x 15x 15x 15x 15x     2x                    
import { isBrowser } from './util';
 
/** Internal {@link ScrollToOptions | `ScrollToOptions`}: `left` and `top` properties always have values */
interface _ScrollPosition extends ScrollToOptions {
    left: number;
    top: number;
}
 
export interface ScrollPositionElement extends ScrollToOptions {
    /**
     * A valid CSS selector. Some special characters need to be escaped (https://mathiasbynens.be/notes/css-escapes).
     * @example
     * Here are some examples:
     *
     * - `.title`
     * - `.content:first-child`
     * - `#marker`
     * - `#marker\~with\~symbols`
     * - `#marker.with.dot`: Selects `class="with dot" id="marker"`, not `id="marker.with.dot"`
     *
     */
    el: string | Element;
}
 
/** Scroll parameters */
export type ScrollPosition = ScrollToOptions | ScrollPositionElement;
 
/** Get current window scroll position */
export const winScrollPos = (): _ScrollPosition => ({
    left: window.scrollX,
    top: window.scrollY
});
 
/** Get element position for scrolling in document */
function getElementPosition(
    el: Element,
    offset: ScrollToOptions
): _ScrollPosition {
    const docRect = document.documentElement.getBoundingClientRect();
    const elRect = el.getBoundingClientRect();
 
    return {
        behavior: offset.behavior,
        left: elRect.left - docRect.left - (offset.left || 0),
        top: elRect.top - docRect.top - (offset.top || 0)
    };
}
 
/** Scroll to specified position */
export function scrollToPosition(position: ScrollPosition): void {
    if ('el' in position) {
        const positionEl = position.el;
 
        const el =
            typeof positionEl === 'string'
                ? document.querySelector(positionEl)
                : positionEl;
 
        if (!el) return;
 
        position = getElementPosition(el, position);
    }
 
    if ('scrollBehavior' in document.documentElement.style) {
        window.scrollTo(position);
    } else {
        window.scrollTo(
            Number.isFinite(position.left) ? position.left! : window.scrollX,
            Number.isFinite(position.top) ? position.top! : window.scrollY
        );
    }
}
 
/** Stored scroll positions */
export const scrollPositions = new Map<string, _ScrollPosition>();
 
const POSITION_KEY = '__scroll_position_key';
 
/** Save scroll position */
export function saveScrollPosition(
    key: string,
    scrollPosition = winScrollPos()
) {
    scrollPosition = { ...scrollPosition };
    scrollPositions.set(key, scrollPosition);
 
    try {
        if (location.href !== key) return;
        // preserve the existing history state as it could be overridden by the user
        const stateCopy = {
            ...(history.state || {}),
            [POSITION_KEY]: scrollPosition
        };
        history.replaceState(stateCopy, '');
    } catch (error) {}
}
 
/** Get saved scroll position */
export function getSavedScrollPosition(
    key: string,
    defaultValue: _ScrollPosition | null = null
): _ScrollPosition | null {
    const scroll = scrollPositions.get(key) || history.state[POSITION_KEY];
 
    // Saved scroll position should not be used multiple times, next time should use newly saved position
    scrollPositions.delete(key);
    return scroll || defaultValue;
}