import getClientRect from '../utils/getClientRect';
import getOppositePlacement from '../utils/getOppositePlacement';
import getOffsetParent from '../utils/getOffsetParent';
import getBoundaries from '../utils/getBoundaries';
/**
* Modifier used to prevent the popper from being positioned outside the boundary.
*
* An scenario exists where the reference itself is not within the boundaries. We can
* say it has "escaped the boundaries" — or just "escaped". In this case we need to
* decide whether the popper should either:
*
* - detach from the reference and remain "trapped" in the boundaries, or
* - if it should be ignore the boundary and "escape with the reference"
*
* When `escapeWithReference` is `true`, and reference is completely outside the
* boundaries, the popper will overflow (or completely leave) the boundaries in order
* to remain attached to the edge of the reference.
*
* @method
* @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 preventOverflow(data, options) {
const boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper);
const boundaries = getBoundaries(data.instance.popper, options.padding, boundariesElement);
options.boundaries = boundaries;
const order = options.priority;
let popper = getClientRect(data.offsets.popper);
const check = {
primary(placement) {
let value = popper[placement];
if (popper[placement] < boundaries[placement] && !shouldOverflowBoundary(data, options, placement)) {
value = Math.max(popper[placement], boundaries[placement]);
}
return { [placement]: value };
},
secondary(placement) {
const mainSide = placement === 'right' ? 'left' : 'top';
let value = popper[mainSide];
if (popper[placement] > boundaries[placement] && !shouldOverflowBoundary(data, options, placement)) {
value = Math.min(popper[mainSide], boundaries[placement] - (placement === 'right' ? popper.width : popper.height));
}
return { [mainSide]: value };
}
}
order.forEach((placement) => {
const side = ['left', 'top'].indexOf(placement) !== -1 ? 'primary' : 'secondary';
popper = {...popper, ...check[side](placement)};
});
data.offsets.popper = popper;
return data;
}
/**
* Determine if the popper should overflow a boundary edge to stay together with the reference.
*/
function shouldOverflowBoundary(data, options, overflowDirection) {
if (!options.escapeWithReference) {
return false;
}
Iif (data.flipped && isSameAxis(data.originalPlacement, overflowDirection)) {
return true;
}
Eif (!isSameAxis(data.originalPlacement, overflowDirection)) {
return true;
}
return true;
}
/**
* Determine if two placement values are on the same axis.
*/
function isSameAxis(a, b) {
// placement syntax:
//
// ( "top" | "right" | "bottom" | "left" ) ( "-start" | "" | "-end" )
// |------------- Direction -------------|
//
const aDirection = a.split('-')[0];
const bDirection = b.split('-')[0];
return aDirection === bDirection || aDirection === getOppositePlacement(b);
}
|