Idea of the project: if someone wants to order a project development, here you can send an application.
import isNumeric from '../utils/isNumeric';
import getClientRect from '../utils/getClientRect';
import find from '../utils/find';
/**
* Converts a string containing value + unit into a px value number
* @function
* @memberof {modifiers~offset}
* @private
* @argument {String} str - Value + unit string
* @argument {String} measurement - `height` or `width`
* @argument {Object} popperOffsets
* @argument {Object} referenceOffsets
* @returns {Number|String}
* Value in pixels, or original string if no values were extracted
*/
export function toValue(str, measurement, popperOffsets, referenceOffsets) {
// separate value from unit
const split = str.match(/((?:\-|\+)?\d*\.?\d*)(.*)/);
const value = +split[1];
const unit = split[2];
// If it's not a number it's an operator, I guess
if (!value) {
return str;
}
if (unit.indexOf('%') === 0) {
let element;
switch (unit) {
case '%p':
element = popperOffsets;
break;
case '%':
case '%r':
default:
element = referenceOffsets;
}
const rect = getClientRect(element);
return rect[measurement] / 100 * value;
} else if (unit === 'vh' || unit === 'vw') {
// if is a vh or vw, we calculate the size based on the viewport
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;
} else {
// if is an explicit pixel unit, we get rid of the unit and keep the value
// if is an implicit unit, it's px, and we return just the value
return value;
}
}
/**
* Parse an `offset` string to extrapolate `x` and `y` numeric offsets.
* @function
* @memberof {modifiers~offset}
* @private
* @argument {String} offset
* @argument {Object} popperOffsets
* @argument {Object} referenceOffsets
* @argument {String} basePlacement
* @returns {Array} a two cells array with x and y offsets in numbers
*/
export function parseOffset(
offset,
popperOffsets,
referenceOffsets,
basePlacement
) {
const offsets = [0, 0];
// 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
const useHeight = ['right', 'left'].indexOf(basePlacement) !== -1;
// Split the offset string to obtain a list of values and operands
// The regex addresses values with the plus or minus sign in front (+10, -20, etc)
const fragments = offset.split(/(\+|\-)/).map(frag => frag.trim());
// Detect if the offset string contains a pair of values or a single one
// they could be separated by comma or space
const divider = fragments.indexOf(
find(fragments, frag => frag.search(/,|\s/) !== -1)
);
if (fragments[divider] && fragments[divider].indexOf(',') === -1) {
console.warn(
'Offsets separated by white space(s) are deprecated, use a comma (,) instead.'
);
}
// If divider is found, we divide the list of values and operands to divide
// them by ofset X and Y.
const splitRegex = /\s*,\s*|\s+/;
let ops = divider !== -1
? [
fragments
.slice(0, divider)
.concat([fragments[divider].split(splitRegex)[0]]),
[fragments[divider].split(splitRegex)[1]].concat(
fragments.slice(divider + 1)
),
]
: [fragments];
// Convert the values with units to absolute pixels to allow our computations
ops = ops.map((op, index) => {
// Most of the units rely on the orientation of the popper
const measurement = (index === 1 ? !useHeight : useHeight)
? 'height'
: 'width';
let mergeWithPrevious = false;
return (
op
// This aggregates any `+` or `-` sign that aren't considered operators
// e.g.: 10 + +5 => [10, +, +5]
.reduce((a, b) => {
if (a[a.length - 1] === '' && ['+', '-'].indexOf(b) !== -1) {
a[a.length - 1] = b;
mergeWithPrevious = true;
return a;
} else if (mergeWithPrevious) {
a[a.length - 1] += b;
mergeWithPrevious = false;
return a;
} else {
return a.concat(b);
}
}, [])
// Here we convert the string values into number values (in px)
.map(str => toValue(str, measurement, popperOffsets, referenceOffsets))
);
});
// Loop trough the offsets arrays and execute the operations
ops.forEach((op, index) => {
op.forEach((frag, index2) => {
if (isNumeric(frag)) {
offsets[index] += frag * (op[index2 - 1] === '-' ? -1 : 1);
}
});
});
return offsets;
}
/**
* @function
* @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
* The offset value as described in the modifier description
* @returns {Object} The data object, properly modified
*/
export default function offset(data, { offset }) {
const { placement, offsets: { popper, reference } } = data;
const basePlacement = placement.split('-')[0];
let offsets;
if (isNumeric(+offset)) {
offsets = [+offset, 0];
} else {
offsets = parseOffset(offset, popper, reference, basePlacement);
}
if (basePlacement === 'left') {
popper.top += offsets[0];
popper.left -= offsets[1];
} else if (basePlacement === 'right') {
popper.top += offsets[0];
popper.left += offsets[1];
} else if (basePlacement === 'top') {
popper.left += offsets[0];
popper.top -= offsets[1];
} else if (basePlacement === 'bottom') {
popper.left += offsets[0];
popper.top += offsets[1];
}
data.popper = popper;
return data;
}