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}`]); const popperBorderSide = parseFloat(css[`border${sideCapitalized}Width`]); 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; }