import getClientRect from '../utils/getClientRect';
|
import getOuterSizes from '../utils/getOuterSizes';
|
import isModifierRequired from '../utils/isModifierRequired';
|
import getStyleComputedProperty from '../utils/getStyleComputedProperty';
|
|
/**
|
* @function
|
* @memberof Modifiers
|
* @argument {Object} data - The data object generated by update method
|
* @argument {Object} options - Modifiers configuration and options
|
* @returns {Object} The data object, properly modified
|
*/
|
export default function arrow(data, options) {
|
// arrow depends on keepTogether in order to work
|
if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) {
|
return data;
|
}
|
|
let arrowElement = options.element;
|
|
// if arrowElement is a string, suppose it's a CSS selector
|
if (typeof arrowElement === 'string') {
|
arrowElement = data.instance.popper.querySelector(arrowElement);
|
|
// if arrowElement is not found, don't run the modifier
|
if (!arrowElement) {
|
return data;
|
}
|
} else {
|
// if the arrowElement isn't a query selector we must check that the
|
// provided DOM node is child of its popper node
|
if (!data.instance.popper.contains(arrowElement)) {
|
console.warn(
|
'WARNING: `arrow.element` must be child of its popper element!'
|
);
|
return data;
|
}
|
}
|
|
const placement = data.placement.split('-')[0];
|
const { popper, reference } = data.offsets;
|
const isVertical = ['left', 'right'].indexOf(placement) !== -1;
|
|
const len = isVertical ? 'height' : 'width';
|
const sideCapitalized = isVertical ? 'Top' : 'Left';
|
const side = sideCapitalized.toLowerCase();
|
const altSide = isVertical ? 'left' : 'top';
|
const opSide = isVertical ? 'bottom' : 'right';
|
const arrowElementSize = getOuterSizes(arrowElement)[len];
|
|
//
|
// extends keepTogether behavior making sure the popper and its
|
// reference have enough pixels in conjunction
|
//
|
|
// top/left side
|
if (reference[opSide] - arrowElementSize < popper[side]) {
|
data.offsets.popper[side] -=
|
popper[side] - (reference[opSide] - arrowElementSize);
|
}
|
// bottom/right side
|
if (reference[side] + arrowElementSize > popper[opSide]) {
|
data.offsets.popper[side] +=
|
reference[side] + arrowElementSize - popper[opSide];
|
}
|
data.offsets.popper = getClientRect(data.offsets.popper);
|
|
// compute center of the popper
|
const center = reference[side] + reference[len] / 2 - arrowElementSize / 2;
|
|
// Compute the sideValue using the updated popper offsets
|
// take popper margin in account because we don't have this info available
|
const css = getStyleComputedProperty(data.instance.popper);
|
const popperMarginSide = parseFloat(css[`margin${sideCapitalized}`], 10);
|
const popperBorderSide = parseFloat(css[`border${sideCapitalized}Width`], 10);
|
let sideValue =
|
center - data.offsets.popper[side] - popperMarginSide - popperBorderSide;
|
|
// prevent arrowElement from being placed not contiguously to its popper
|
sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0);
|
|
data.arrowElement = arrowElement;
|
data.offsets.arrow = {
|
[side]: Math.round(sideValue),
|
[altSide]: '', // make sure to unset any eventual altSide value from the DOM node
|
};
|
|
return data;
|
}
|