all files / popper/modifiers/ offset.js

83.33% Statements 35/42
76.19% Branches 32/42
100% Functions 2/2
83.33% Lines 35/42
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 110 111                                            125× 125×     125× 122×                                                                                             125× 15× 15×   110× 22× 22×   88× 42× 42×   46× 46× 46×   125×    
import isNumeric from '../utils/isNumeric';
import getClientRect from '../utils/getClientRect';
 
/**
 * Modifier used to add an offset to the popper, useful if you more granularity positioning your popper.
 * The offsets will shift the popper on the side of its reference element.
 * @method
 * @memberof Modifiers
 * @argument {Object} data - The data object generated by update method
 * @argument {Object} options - Modifiers configuration and options
 * @argument {Number|String} options.offset=0
 *      Basic usage allows a number used to nudge the popper by the given amount of pixels.
 *      You can pass a percentage value as string (eg. `20%`) to nudge by the given percentage (relative to reference element size)
 *      Other supported units are `vh` and `vw` (relative to viewport)
 *      Additionally, you can pass a pair of values (eg. `10 20` or `2vh 20%`) to nudge the popper
 *      on both axis.
 *      A note about percentage values, if you want to refer a percentage to the popper size instead of the reference element size,
 *      use `%p` instead of `%` (eg: `20%p`). To make it clearer, you can replace `%` with `%r` and use eg.`10%p 25%r`.
 *      > **Heads up!** The order of the axis is relative to the popper placement: `bottom` or `top` are `X,Y`, the other are `Y,X`
 * @returns {Object} The data object, properly modified
 */
export default function offset(data, options) {
    const placement = data.placement;
    const popper  = data.offsets.popper;
 
    let offsets;
    if (isNumeric(options.offset)) {
        offsets = [options.offset, 0];
    } else {
        // split the offset in case we are providing a pair of offsets separated
        // by a blank space
        offsets = options.offset.split(' ');
 
        // itherate through each offset to compute them in case they are percentages
        offsets = offsets.map((offset, index) => {
            // separate value from unit
            const split = offset.match(/(\d*\.?\d*)(.*)/);
            const value = +split[1];
            const unit = split[2];
 
            // use height if placement is left or right and index is 0 otherwise use width
            // in this way the first offset will use an axis and the second one
            // will use the other one
            let useHeight = placement.indexOf('right') !== -1 || placement.indexOf('left') !== -1;
 
            if (index === 1) {
                useHeight = !useHeight;
            }
 
            const measurement = useHeight ? 'height' : 'width';
 
            // if is a percentage relative to the popper (%p), we calculate the value of it using
            // as base the sizes of the popper
            // if is a percentage (% or %r), we calculate the value of it using as base the
            // sizes of the reference element
            if (unit.indexOf('%') === 0) {
                let element;
                switch(unit) {
                    case '%p':
                        element = data.offsets.popper;
                        break;
                    case '%':
                    case '$r':
                    default:
                        element = data.offsets.reference;
                }
 
                const rect = getClientRect(element);
                const len = rect[measurement];
                return (len / 100) * value;
            }
            // if is a vh or vw, we calculate the size based on the viewport
            else Iif (unit === 'vh' || unit === 'vw') {
                let size;
                if (unit === 'vh') {
                    size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
                } else {
                    size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
                }
                return (size / 100) * value;
            }
            // if is an explicit pixel unit, we get rid of the unit and keep the value
            else Iif (unit === 'px') {
                return +value;
            }
            // if is an implicit unit, it's px, and we return just the value
            else {
                return +offset;
            }
        });
    }
 
    if (data.placement.indexOf('left') !== -1) {
        popper.top += offsets[0];
        popper.left -= offsets[1] || 0;
    }
    else if (data.placement.indexOf('right') !== -1) {
        popper.top += offsets[0];
        popper.left += offsets[1] || 0;
    }
    else if (data.placement.indexOf('top') !== -1) {
        popper.left += offsets[0];
        popper.top -= offsets[1] || 0;
    }
    else Eif (data.placement.indexOf('bottom') !== -1) {
        popper.left += offsets[0];
        popper.top += offsets[1] || 0;
    }
    return data;
}