diff options
Diffstat (limited to 'popperjs/package/lib/modifiers/flip.js.flow')
-rw-r--r-- | popperjs/package/lib/modifiers/flip.js.flow | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/popperjs/package/lib/modifiers/flip.js.flow b/popperjs/package/lib/modifiers/flip.js.flow new file mode 100644 index 0000000..c74b852 --- /dev/null +++ b/popperjs/package/lib/modifiers/flip.js.flow @@ -0,0 +1,177 @@ +// @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); |