178 lines
4.8 KiB
Text
178 lines
4.8 KiB
Text
|
// @flow
|
|||
|
import type { Placement, Boundary, RootBoundary } from '../enums';
|
|||
|
import type { ModifierArguments, Modifier, Padding } from '../types';
|
|||
|
import getOppositePlacement from '../utils/getOppositePlacement';
|
|||
|
import getBasePlacement from '../utils/getBasePlacement';
|
|||
|
import getOppositeVariationPlacement from '../utils/getOppositeVariationPlacement';
|
|||
|
import detectOverflow from '../utils/detectOverflow';
|
|||
|
import computeAutoPlacement from '../utils/computeAutoPlacement';
|
|||
|
import { bottom, top, start, right, left, auto } from '../enums';
|
|||
|
import getVariation from '../utils/getVariation';
|
|||
|
|
|||
|
// eslint-disable-next-line import/no-unused-modules
|
|||
|
export type Options = {
|
|||
|
mainAxis: boolean,
|
|||
|
altAxis: boolean,
|
|||
|
fallbackPlacements: Array<Placement>,
|
|||
|
padding: Padding,
|
|||
|
boundary: Boundary,
|
|||
|
rootBoundary: RootBoundary,
|
|||
|
altBoundary: boolean,
|
|||
|
flipVariations: boolean,
|
|||
|
allowedAutoPlacements: Array<Placement>,
|
|||
|
};
|
|||
|
|
|||
|
function getExpandedFallbackPlacements(placement: Placement): Array<Placement> {
|
|||
|
if (getBasePlacement(placement) === auto) {
|
|||
|
return [];
|
|||
|
}
|
|||
|
|
|||
|
const oppositePlacement = getOppositePlacement(placement);
|
|||
|
|
|||
|
return [
|
|||
|
getOppositeVariationPlacement(placement),
|
|||
|
oppositePlacement,
|
|||
|
getOppositeVariationPlacement(oppositePlacement),
|
|||
|
];
|
|||
|
}
|
|||
|
|
|||
|
function flip({ state, options, name }: ModifierArguments<Options>) {
|
|||
|
if (state.modifiersData[name]._skip) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
const {
|
|||
|
mainAxis: checkMainAxis = true,
|
|||
|
altAxis: checkAltAxis = true,
|
|||
|
fallbackPlacements: specifiedFallbackPlacements,
|
|||
|
padding,
|
|||
|
boundary,
|
|||
|
rootBoundary,
|
|||
|
altBoundary,
|
|||
|
flipVariations = true,
|
|||
|
allowedAutoPlacements,
|
|||
|
} = options;
|
|||
|
|
|||
|
const preferredPlacement = state.options.placement;
|
|||
|
const basePlacement = getBasePlacement(preferredPlacement);
|
|||
|
const isBasePlacement = basePlacement === preferredPlacement;
|
|||
|
|
|||
|
const fallbackPlacements =
|
|||
|
specifiedFallbackPlacements ||
|
|||
|
(isBasePlacement || !flipVariations
|
|||
|
? [getOppositePlacement(preferredPlacement)]
|
|||
|
: getExpandedFallbackPlacements(preferredPlacement));
|
|||
|
|
|||
|
const placements = [preferredPlacement, ...fallbackPlacements].reduce(
|
|||
|
(acc, placement) => {
|
|||
|
return acc.concat(
|
|||
|
getBasePlacement(placement) === auto
|
|||
|
? computeAutoPlacement(state, {
|
|||
|
placement,
|
|||
|
boundary,
|
|||
|
rootBoundary,
|
|||
|
padding,
|
|||
|
flipVariations,
|
|||
|
allowedAutoPlacements,
|
|||
|
})
|
|||
|
: placement
|
|||
|
);
|
|||
|
},
|
|||
|
[]
|
|||
|
);
|
|||
|
|
|||
|
const referenceRect = state.rects.reference;
|
|||
|
const popperRect = state.rects.popper;
|
|||
|
|
|||
|
const checksMap = new Map();
|
|||
|
let makeFallbackChecks = true;
|
|||
|
let firstFittingPlacement = placements[0];
|
|||
|
|
|||
|
for (let i = 0; i < placements.length; i++) {
|
|||
|
const placement = placements[i];
|
|||
|
const basePlacement = getBasePlacement(placement);
|
|||
|
const isStartVariation = getVariation(placement) === start;
|
|||
|
const isVertical = [top, bottom].indexOf(basePlacement) >= 0;
|
|||
|
const len = isVertical ? 'width' : 'height';
|
|||
|
|
|||
|
const overflow = detectOverflow(state, {
|
|||
|
placement,
|
|||
|
boundary,
|
|||
|
rootBoundary,
|
|||
|
altBoundary,
|
|||
|
padding,
|
|||
|
});
|
|||
|
|
|||
|
let mainVariationSide: any = isVertical
|
|||
|
? isStartVariation
|
|||
|
? right
|
|||
|
: left
|
|||
|
: isStartVariation
|
|||
|
? bottom
|
|||
|
: top;
|
|||
|
|
|||
|
if (referenceRect[len] > popperRect[len]) {
|
|||
|
mainVariationSide = getOppositePlacement(mainVariationSide);
|
|||
|
}
|
|||
|
|
|||
|
const altVariationSide: any = getOppositePlacement(mainVariationSide);
|
|||
|
|
|||
|
const checks = [];
|
|||
|
|
|||
|
if (checkMainAxis) {
|
|||
|
checks.push(overflow[basePlacement] <= 0);
|
|||
|
}
|
|||
|
|
|||
|
if (checkAltAxis) {
|
|||
|
checks.push(
|
|||
|
overflow[mainVariationSide] <= 0,
|
|||
|
overflow[altVariationSide] <= 0
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
if (checks.every((check) => check)) {
|
|||
|
firstFittingPlacement = placement;
|
|||
|
makeFallbackChecks = false;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
checksMap.set(placement, checks);
|
|||
|
}
|
|||
|
|
|||
|
if (makeFallbackChecks) {
|
|||
|
// `2` may be desired in some cases – research later
|
|||
|
const numberOfChecks = flipVariations ? 3 : 1;
|
|||
|
|
|||
|
for (let i = numberOfChecks; i > 0; i--) {
|
|||
|
const fittingPlacement = placements.find((placement) => {
|
|||
|
const checks = checksMap.get(placement);
|
|||
|
if (checks) {
|
|||
|
return checks.slice(0, i).every((check) => check);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
if (fittingPlacement) {
|
|||
|
firstFittingPlacement = fittingPlacement;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (state.placement !== firstFittingPlacement) {
|
|||
|
state.modifiersData[name]._skip = true;
|
|||
|
state.placement = firstFittingPlacement;
|
|||
|
state.reset = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// eslint-disable-next-line import/no-unused-modules
|
|||
|
export type FlipModifier = Modifier<'flip', Options>;
|
|||
|
export default ({
|
|||
|
name: 'flip',
|
|||
|
enabled: true,
|
|||
|
phase: 'main',
|
|||
|
fn: flip,
|
|||
|
requiresIfExists: ['offset'],
|
|||
|
data: { _skip: false },
|
|||
|
}: FlipModifier);
|