@popperjs/core
Version:
Tooltip and Popover Positioning Engine
100 lines (84 loc) • 3 kB
JavaScript
// @flow
import type { Modifier, ModifierArguments, Padding } from '../types';
import getBasePlacement from '../utils/getBasePlacement';
import getLayoutRect from '../dom-utils/getLayoutRect';
import contains from '../dom-utils/contains';
import getMainAxisFromPlacement from '../utils/getMainAxisFromPlacement';
import within from '../utils/within';
import mergePaddingObject from '../utils/mergePaddingObject';
import expandToHashMap from '../utils/expandToHashMap';
import { left, right, basePlacements, top, bottom } from '../enums';
type Options = {
element: HTMLElement | string,
padding: Padding,
};
function arrow({ state, name }: ModifierArguments<Options>) {
const arrowElement = state.elements.arrow;
const popperOffsets = state.modifiersData.popperOffsets;
const basePlacement = getBasePlacement(state.placement);
const axis = getMainAxisFromPlacement(basePlacement);
const isVertical = [left, right].indexOf(basePlacement) >= 0;
const len = isVertical ? 'height' : 'width';
if (!arrowElement) {
return;
}
const paddingObject = state.modifiersData[`${name}#persistent`].padding;
const arrowRect = getLayoutRect(arrowElement);
const minProp = axis === 'y' ? top : left;
const maxProp = axis === 'y' ? bottom : right;
const endDiff =
state.rects.reference[len] +
state.rects.reference[axis] -
popperOffsets[axis] -
state.rects.popper[len];
const startDiff = popperOffsets[axis] - state.rects.reference[axis];
const centerToReference = endDiff / 2 - startDiff / 2;
// Make sure the arrow doesn't overflow the popper if the center point is
// outside of the popper bounds
const center = within(
paddingObject[minProp],
state.rects.popper[len] / 2 - arrowRect[len] / 2 + centerToReference,
state.rects.popper[len] - arrowRect[len] - paddingObject[maxProp]
);
// Prevents breaking syntax highlighting...
const axisProp: string = axis;
state.modifiersData[name] = { [axisProp]: center };
}
function effect({ state, options, name }: ModifierArguments<Options>) {
let { element: arrowElement = '[data-popper-arrow]', padding = 0 } = options;
// CSS selector
if (typeof arrowElement === 'string') {
arrowElement = state.elements.popper.querySelector(arrowElement);
if (!arrowElement) {
return;
}
}
if (!contains(state.elements.popper, arrowElement)) {
if (__DEV__) {
console.error(
[
'Popper: "arrow" modifier\'s `element` must be a child of the popper',
'element.',
].join(' ')
);
}
return;
}
state.elements.arrow = arrowElement;
state.modifiersData[`${name}#persistent`] = {
padding: mergePaddingObject(
typeof padding !== 'number'
? padding
: expandToHashMap(padding, basePlacements)
),
};
}
export default ({
name: 'arrow',
enabled: true,
phase: 'main',
fn: arrow,
effect,
requires: ['popperOffsets'],
requiresIfExists: ['preventOverflow'],
}: Modifier<Options>);