import { PopoverSide, PopoverAlignment, RectCollisions } from '../Types' import { getAppRect, getPositionedPopoverRect } from './Rect' export const OppositeSide: Record = { top: 'bottom', bottom: 'top', left: 'right', right: 'left', } export const getOverflows = (popoverRect: DOMRect, documentRect: DOMRect): Record => { const overflows = { top: documentRect.top - popoverRect.top, bottom: popoverRect.height - (documentRect.bottom - popoverRect.top), left: documentRect.left - popoverRect.left, right: popoverRect.right - documentRect.right, } return overflows } export const checkCollisions = (popoverRect: DOMRect, containerRect: DOMRect): RectCollisions => { const appRect = getAppRect(containerRect) return { top: popoverRect.top < appRect.top, left: popoverRect.left < appRect.left, bottom: popoverRect.bottom > appRect.bottom, right: popoverRect.right > appRect.right, } } export const getNonCollidingSide = ( preferredSide: PopoverSide, preferredSideCollisions: RectCollisions, oppositeSideCollisions: RectCollisions, ): PopoverSide => { const oppositeSide = OppositeSide[preferredSide] return preferredSideCollisions[preferredSide] && !oppositeSideCollisions[oppositeSide] ? oppositeSide : preferredSide } const OppositeAlignment: Record, PopoverAlignment> = { start: 'end', end: 'start', } export const getNonCollidingAlignment = ( finalSide: PopoverSide, preferredAlignment: PopoverAlignment, collisions: RectCollisions, { popoverRect, buttonRect, documentRect, }: { popoverRect: DOMRect buttonRect: DOMRect documentRect: DOMRect }, ): PopoverAlignment => { const isHorizontalSide = finalSide === 'top' || finalSide === 'bottom' const boundToCheckForStart = isHorizontalSide ? 'right' : 'bottom' const boundToCheckForEnd = isHorizontalSide ? 'left' : 'top' const prefersAligningAtStart = preferredAlignment === 'start' const prefersAligningAtCenter = preferredAlignment === 'center' const prefersAligningAtEnd = preferredAlignment === 'end' if (prefersAligningAtCenter) { if (collisions[boundToCheckForStart]) { return 'end' } if (collisions[boundToCheckForEnd]) { return 'start' } } else { const oppositeAlignmentCollisions = checkCollisions( getPositionedPopoverRect(popoverRect, buttonRect, finalSide, OppositeAlignment[preferredAlignment]), documentRect, ) if ( prefersAligningAtStart && collisions[boundToCheckForStart] && !oppositeAlignmentCollisions[boundToCheckForEnd] ) { return 'end' } if (prefersAligningAtEnd && collisions[boundToCheckForEnd] && !oppositeAlignmentCollisions[boundToCheckForStart]) { return 'start' } } return preferredAlignment }