All files / lib/util layout.ts

100% Statements 79/79
100% Branches 2/2
100% Functions 2/2
100% Lines 79/79

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 801x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x 19x  
import getCaretPosition from 'textarea-caret';
import { AcceptedInputType } from '../types';
import { getInputSelectionStart } from './input';
 
export type Point = {
    top: number;
    left: number;
};
 
/**
 * Everything the dropdown needs to know about the page, measured in viewport coordinates.
 *
 * @note Facts only. Deciding what to do with them is `getDropdownPosition`.
 */
export type CaretMetrics = {
    /**
     * Position of the caret, plus the height of the line it sits on
     */
    caret: Point & { height: number };
 
    /**
     * Visible content box of the input, ie the area the caret can be seen in
     */
    box: {
        top: number;
        bottom: number;
        left: number;
        right: number;
    };
 
    /**
     * Origin the dropdown is positioned against, ie the input's offsetParent
     */
    origin: Point;
 
    /**
     * Size of the visible page
     */
    viewport: {
        width: number;
        height: number;
    };
 
    /**
     * Whether the input scrolls its own content, ie whether it can hide its own caret
     */
    scrollable: boolean;
};
 
export function getCaretMetrics(input: AcceptedInputType): CaretMetrics {
    const caret = getCaretPosition(input, getInputSelectionStart(input));
    const box = input.getBoundingClientRect();
 
    return {
        // `textarea-caret` measures against the unscrolled content, so remove the element's own scroll
        caret: {
            top: box.top + caret.top - input.scrollTop,
            left: box.left + caret.left - input.scrollLeft,
            height: caret.height,
        },
        // Content is clipped at the padding box, which `clientTop`/`clientLeft` (the border widths) offset to
        box: {
            top: box.top + input.clientTop,
            bottom: box.top + input.clientTop + input.clientHeight,
            left: box.left + input.clientLeft,
            right: box.left + input.clientLeft + input.clientWidth,
        },
        origin: {
            top: box.top - input.offsetTop,
            left: box.left - input.offsetLeft,
        },
        viewport: {
            width: window.innerWidth,
            height: window.innerHeight,
        },
        // An element that is not laid out reports a zero-sized box, which is not the same as one that scrolls
        scrollable: input.scrollHeight > input.clientHeight,
    };
}