import { Rect } from '../../esl-utils/dom/rect' ; export type PositionType = 'top' | 'bottom' | 'left' | 'right' ; export interface Point { x : number ; y : number ; } export interface PopupPositionValue { placedAt : PositionType ; popup : Rect ; arrow : Point ; } export interface IntersectionRatioRect { top ? : number ; left ? : number ; right ? : number ; bottom ? : number ; } export interface PopupPositionConfig { position : PositionType ; behavior : string ; marginArrow : number ; offsetArrowRatio : number ; intersectionRatio : IntersectionRatioRect ; arrow : DOMRect | Rect ; element : DOMRect ; inner : Rect ; outer : Rect ; trigger : Rect ; } param position export function isMajorAxisHorizontal ( position ) { return [ 'left' , 'right' ] . includes ( position ) ; } param cfg param centerPosition param dimensionName function calcPopupPositionByMinorAxis ( cfg , centerPosition , dimensionName ) { return centerPosition - cfg . arrow [ dimensionName ] / 2 - cfg . marginArrow - calcUsableSizeForArrow ( cfg , dimensionName ) * cfg . offsetArrowRatio ; } param cfg function calcPopupBasicRect ( cfg ) { let x = calcPopupPositionByMinorAxis ( cfg , cfg . inner . cx , 'width' ) ; let y = cfg . inner . y - cfg . element . height ; switch ( cfg . position ) { case 'left' : x = cfg . inner . x - cfg . element . width ; y = calcPopupPositionByMinorAxis ( cfg , cfg . inner . cy , 'height' ) ; break ; case 'right' : x = cfg . inner . right ; y = calcPopupPositionByMinorAxis ( cfg , cfg . inner . cy , 'height' ) ; break ; case 'bottom' : x = calcPopupPositionByMinorAxis ( cfg , cfg . inner . cx , 'width' ) ; y = cfg . inner . bottom ; break ; } return new Rect ( x , y , cfg . element . width , cfg . element . height ) ; } param position function getOppositePosition ( position ) { return ( { top : 'bottom' , left : 'right' , right : 'left' , bottom : 'top' } [ position ] || position ) as PositionType ; } param cfg param rect param arrow function fitOnMajorAxis ( cfg , rect , arrow ) { if ( cfg . behavior !== 'fit' && cfg . behavior !== 'fit-on-major' ) return cfg . position ; let isMirrored = false ; const actionsToFit = { 'bottom' : ( ) => { if ( cfg . intersectionRatio . bottom || cfg . outer . bottom < rect . bottom ) { rect . y = cfg . inner . top - cfg . element . height ; isMirrored = true ; } } , 'left' : ( ) => { if ( cfg . intersectionRatio . left || rect . x < cfg . outer . x ) { rect . x = cfg . inner . right ; isMirrored = true ; } } , 'right' : ( ) => { if ( cfg . intersectionRatio . right || cfg . outer . right < rect . right ) { rect . x = cfg . inner . x - cfg . element . width ; isMirrored = true ; } } , 'top' : ( ) => { if ( cfg . intersectionRatio . top || rect . y < cfg . outer . y ) { rect . y = cfg . inner . bottom ; isMirrored = true ; } } } ; actionsToFit [ cfg . position ] ( ) ; return isMirrored ? getOppositePosition ( cfg . position ) : cfg . position ; } param cfg param rect param arrow function fitOnMinorAxisHorizontal ( cfg , rect , arrow ) { if ( cfg . outer . width < cfg . element . width || cfg . trigger . x < cfg . outer . x || cfg . trigger . right > cfg . outer . right ) return ; let arrowAdjust = 0 ; if ( rect . x < cfg . outer . x ) { arrowAdjust = rect . x - cfg . outer . x ; rect . x = cfg . outer . x ; } if ( rect . right > cfg . outer . right ) { arrowAdjust = rect . right - cfg . outer . right ; rect . x -= arrowAdjust ; } arrow . x += arrowAdjust ; } param cfg param rect param arrow function fitOnMinorAxisVertical ( cfg , rect , arrow ) { if ( cfg . outer . height < cfg . element . height || cfg . trigger . y < cfg . outer . y || cfg . trigger . bottom > cfg . outer . bottom ) return ; let arrowAdjust = 0 ; if ( rect . y < cfg . outer . y ) { arrowAdjust = rect . y - cfg . outer . y ; rect . y = cfg . outer . y ; } if ( rect . bottom > cfg . outer . bottom ) { arrowAdjust = rect . bottom - cfg . outer . bottom ; rect . y -= arrowAdjust ; } arrow . y += arrowAdjust ; } param cfg param rect param arrow function fitOnMinorAxis ( cfg , rect , arrow ) { if ( cfg . behavior !== 'fit' && cfg . behavior !== 'fit-on-minor' ) return ; if ( isMajorAxisHorizontal ( cfg . position ) ) { fitOnMinorAxisVertical ( cfg , rect , arrow ) ; } else { fitOnMinorAxisHorizontal ( cfg , rect , arrow ) ; } } param cfg param dimensionName function calcUsableSizeForArrow ( cfg , dimensionName ) { return cfg . element [ dimensionName ] - cfg . arrow [ dimensionName ] - 2 * cfg . marginArrow ; } param cfg param dimensionName function calcArrowPosition ( cfg , dimensionName ) { return cfg . marginArrow + calcUsableSizeForArrow ( cfg , dimensionName ) * cfg . offsetArrowRatio ; } param cfg export function calcPopupPosition ( cfg ) { const popup = calcPopupBasicRect ( cfg ) ; const arrow = { x : calcArrowPosition ( cfg , 'width' ) , y : calcArrowPosition ( cfg , 'height' ) , position : cfg . position } ; const placedAt = fitOnMajorAxis ( cfg , popup , arrow ) ; fitOnMinorAxis ( cfg , popup , arrow ) ; return { popup , placedAt , arrow } ; }