import * as CSS from 'csstype' ; const SIDE_OPTIONS = [ 'top' , 'right' , 'bottom' , 'left' ] as const ; const ALIGN_OPTIONS = [ 'start' , 'center' , 'end' ] as const ; type Axis = 'x' | 'y' ; type Side = typeof SIDE_OPTIONS [ number ] ; type Align = typeof ALIGN_OPTIONS [ number ] ; type Point = { x : number ; y : number ; } ; type Size = { width : number ; height : number ; } ; type GetPlacementDataOptions = { /** The rect of the anchor we are placing around */ anchorRect ? : ClientRect ; /** The size of the popper to place */ popperSize ? : Size ; /** An optional arrow size */ arrowSize ? : Size ; /** An optional arrow offset (along the side, default: 0) */ arrowOffset ? : number ; /** The desired side */ side : Side ; /** An optional side offset (distance from the side, default: 0)  */ sideOffset ? : number ; /** The desired alignment */ align : Align ; /** An optional alignment offset (distance along the side, default: 0) */ alignOffset ? : number ; /** An option to turn on/off the collision handling (default: true) */ shouldAvoidCollisions ? : boolean ; /** The rect which represents the boundaries for collision checks */ collisionBoundariesRect ? : ClientRect ; /** The tolerance used for collisions, ie. if we want them to trigger a bit earlier (default: 0) */ collisionTolerance ? : number ; } ; type PlacementData = { popperStyles : CSS . Properties ; arrowStyles : CSS . Properties ; placedSide ? : Side ; placedAlign ? : Align ; } ; /**
 * Given all the information necessary to compute it,
 * this function calculates all the necessary placement data.
 *
 * It will return:
 *
 * - the styles to apply to the popper (including a custom property that is useful to set the transform origin in the right place)
 * - the styles to apply to the arrow
 * - the placed side (because it might have changed because of collisions)
 * - the placed align (because it might have changed because of collisions)
 */ function getPlacementData ( { anchorRect , popperSize , arrowSize , arrowOffset = 0 , side , sideOffset = 0 , align , alignOffset = 0 , shouldAvoidCollisions = true , collisionBoundariesRect , collisionTolerance = 0 , } ) { if ( ! anchorRect || ! popperSize || ! collisionBoundariesRect ) { return { popperStyles : UNMEASURED_POPPER_STYLES , arrowStyles : UNMEASURED_ARROW_STYLES , } ; } const allPlacementPoints = getAllPlacementPoints ( popperSize , anchorRect , sideOffset , alignOffset , arrowSize ) ; const popperPoint = allPlacementPoints [ side ] [ align ] ; if ( shouldAvoidCollisions === false ) { const popperStyles = getPlacementStylesForPoint ( popperPoint ) ; let arrowStyles = UNMEASURED_ARROW_STYLES ; if ( arrowSize ) { arrowStyles = getPopperArrowStyles ( { popperSize , arrowSize , arrowOffset , side , align } ) ; } const transformOrigin = getTransformOrigin ( popperSize , side , align , arrowOffset , arrowSize ) ; return { popperStyles : { ... popperStyles , [ '--radix-popper-transform-origin' as any ] : transformOrigin , } , arrowStyles , placedSide : side , placedAlign : align , } ; } const popperRect = DOMRect . fromRect ( { ... popperSize , ... popperPoint } ) ; const collisionBoundariesRectWithTolerance = getContractedRect ( collisionBoundariesRect , collisionTolerance ) ; const popperCollisions = getCollisions ( popperRect , collisionBoundariesRectWithTolerance ) ; const oppositeSide = getOppositeSide ( side ) ; const oppositeSidePopperPoint = allPlacementPoints [ oppositeSide ] [ align ] ; const updatedOppositeSidePopperPoint = DOMRect . fromRect ( { ... popperSize , ... oppositeSidePopperPoint , } ) ; const oppositeSidePopperCollisions = getCollisions ( updatedOppositeSidePopperPoint , collisionBoundariesRectWithTolerance ) ; const placedSide = getSideAccountingForCollisions ( side , popperCollisions , oppositeSidePopperCollisions ) ; const placedAlign = getAlignAccountingForCollisions ( popperSize , anchorRect , side , align , popperCollisions ) ; const placedPopperPoint = allPlacementPoints [ placedSide ] [ placedAlign ] ; const popperStyles = getPlacementStylesForPoint ( placedPopperPoint ) ; let arrowStyles = UNMEASURED_ARROW_STYLES ; if ( arrowSize ) { arrowStyles = getPopperArrowStyles ( { popperSize , arrowSize , arrowOffset , side : placedSide , align : placedAlign , } ) ; } const transformOrigin = getTransformOrigin ( popperSize , placedSide , placedAlign , arrowOffset , arrowSize ) ; return { popperStyles : { ... popperStyles , [ '--radix-popper-transform-origin' as any ] : transformOrigin , } , arrowStyles , placedSide , placedAlign , } ; } type AllPlacementPoints = Record < Side , Record < Align , Point > > ; function getAllPlacementPoints ( popperSize , anchorRect , sideOffset = 0 , alignOffset = 0 , arrowSize ? ) { const arrowBaseToTipLength = arrowSize ? arrowSize . height : 0 ; const x = getPopperSlotsForAxis ( anchorRect , popperSize , 'x' ) ; const y = getPopperSlotsForAxis ( anchorRect , popperSize , 'y' ) ; const topY = y . before - sideOffset - arrowBaseToTipLength ; const bottomY = y . after + sideOffset + arrowBaseToTipLength ; const leftX = x . before - sideOffset - arrowBaseToTipLength ; const rightX = x . after + sideOffset + arrowBaseToTipLength ; const map = { top : { start : { x : x . start + alignOffset , y : topY } , center : { x : x . center , y : topY } , end : { x : x . end - alignOffset , y : topY } , } , right : { start : { x : rightX , y : y . start + alignOffset } , center : { x : rightX , y : y . center } , end : { x : rightX , y : y . end - alignOffset } , } , bottom : { start : { x : x . start + alignOffset , y : bottomY } , center : { x : x . center , y : bottomY } , end : { x : x . end - alignOffset , y : bottomY } , } , left : { start : { x : leftX , y : y . start + alignOffset } , center : { x : leftX , y : y . center } , end : { x : leftX , y : y . end - alignOffset } , } , } ; return map ; } function getPopperSlotsForAxis ( anchorRect , popperSize , axis ) { const startSide = axis === 'x' ? 'left' : 'top' ; const anchorStart = anchorRect [ startSide ] ; const dimension = axis === 'x' ? 'width' : 'height' ; const anchorDimension = anchorRect [ dimension ] ; const popperDimension = popperSize [ dimension ] ; return { before : anchorStart - popperDimension , start : anchorStart , center : anchorStart + ( anchorDimension - popperDimension ) / 2 , end : anchorStart + anchorDimension - popperDimension , after : anchorStart + anchorDimension , } ; } /**
 * Gets an adjusted side based on collision information
 */ function getSideAccountingForCollisions ( /** The side we want to ideally position to */ side , /** The collisions for this given side */ collisions , /** The collisions for the opposite side (if we were to swap side) */ oppositeSideCollisions ) { const oppositeSide = getOppositeSide ( side ) ; return collisions [ side ] && ! oppositeSideCollisions [ oppositeSide ] ? oppositeSide : side ; } /**
 * Gets an adjusted alignment based on collision information
 */ function getAlignAccountingForCollisions ( /** The size of the popper to place */ popperSize , /** The size of the anchor we are placing around */ anchorSize , /** The final side */ side , /** The desired align */ align , /** The collisions */ collisions ) { const isHorizontalSide = side === 'top' || side === 'bottom' ; const startBound = isHorizontalSide ? 'left' : 'top' ; const endBound = isHorizontalSide ? 'right' : 'bottom' ; const dimension = isHorizontalSide ? 'width' : 'height' ; const isAnchorBigger = anchorSize [ dimension ] > popperSize [ dimension ] ; if ( align === 'start' || align === 'center' ) { if ( ( collisions [ startBound ] && isAnchorBigger ) || ( collisions [ endBound ] && ! isAnchorBigger ) ) { return 'end' ; } } if ( align === 'end' || align === 'center' ) { if ( ( collisions [ endBound ] && isAnchorBigger ) || ( collisions [ startBound ] && ! isAnchorBigger ) ) { return 'start' ; } } return align ; } function getPlacementStylesForPoint ( point ) { const x = Math . round ( point . x + window . scrollX ) ; const y = Math . round ( point . y + window . scrollY ) ; return { position : 'absolute' , top : 0 , left : 0 , minWidth : 'max-content' , willChange : 'transform' , transform : `translate3d(${ x }px, ${ y }px, 0)` , } ; } function getTransformOrigin ( popperSize , side , align , arrowOffset , arrowSize ? ) { const isHorizontalSide = side === 'top' || side === 'bottom' ; const arrowBaseLength = arrowSize ? arrowSize . width : 0 ; const arrowBaseToTipLength = arrowSize ? arrowSize . height : 0 ; const sideOffset = arrowBaseToTipLength ; const alignOffset = arrowBaseLength / 2 + arrowOffset ; let x = '' ; let y = '' ; if ( isHorizontalSide ) { x = { start : `${ alignOffset }px` , center : 'center' , end : `${ popperSize . width - alignOffset }px` , } [ align ] ; y = side === 'top' ? `${ popperSize . height + sideOffset }px` : `${ - sideOffset }px` ; } else { x = side === 'left' ? `${ popperSize . width + sideOffset }px` : `${ - sideOffset }px` ; y = { start : `${ alignOffset }px` , center : 'center' , end : `${ popperSize . height - alignOffset }px` , } [ align ] ; } return `${ x } ${ y }` ; } const UNMEASURED_POPPER_STYLES = { position : 'fixed' , top : 0 , left : 0 , opacity : 0 , transform : 'translate3d(0, -200%, 0)' , } ; const UNMEASURED_ARROW_STYLES = { position : 'absolute' , opacity : 0 , } ; type GetArrowStylesOptions = { /** The size of the popper to place */ popperSize : Size ; /** The size of the arrow itself */ arrowSize : Size ; /** An offset for the arrow along the align axis */ arrowOffset : number ; /** The side where the arrow points to */ side : Side ; /** The alignment of the arrow along the side */ align : Align ; } ; /**
 * Computes the styles necessary to position, rotate and align the arrow correctly.
 * It can adjust itself based on anchor/popper size, side/align and an optional offset.
 */ function getPopperArrowStyles ( { popperSize , arrowSize , arrowOffset , side , align , } ) { const popperCenterX = ( popperSize . width - arrowSize . width ) / 2 ; const popperCenterY = ( popperSize . height - arrowSize . width ) / 2 ; const rotationMap = { top : 0 , right : 90 , bottom : 180 , left : - 90 } ; const rotation = rotationMap [ side ] ; const arrowMaxDimension = Math . max ( arrowSize . width , arrowSize . height ) ; const styles = { width : `${ arrowMaxDimension }px` , height : `${ arrowMaxDimension }px` , transform : `rotate(${ rotation }deg)` , willChange : 'transform' , position : 'absolute' , [ side ] : '100%' , direction : getArrowCssDirection ( side , align ) , } ; if ( side === 'top' || side === 'bottom' ) { if ( align === 'start' ) { styles . left = `${ arrowOffset }px` ; } if ( align === 'center' ) { styles . left = `${ popperCenterX }px` ; } if ( align === 'end' ) { styles . right = `${ arrowOffset }px` ; } } if ( side === 'left' || side === 'right' ) { if ( align === 'start' ) { styles . top = `${ arrowOffset }px` ; } if ( align === 'center' ) { styles . top = `${ popperCenterY }px` ; } if ( align === 'end' ) { styles . bottom = `${ arrowOffset }px` ; } } return styles ; } /**
 * Adjusts the arrow's CSS direction (`ltr` / `rtl`)
 */ function getArrowCssDirection ( side , align ) { if ( ( side === 'top' || side === 'right' ) && align === 'end' ) { return 'rtl' ; } if ( ( side === 'bottom' || side === 'left' ) && align !== 'end' ) { return 'rtl' ; } return 'ltr' ; } /**
 * Gets the opposite side of a given side (ie. top => bottom, left => right, …)
 */ function getOppositeSide ( side ) { const oppositeSides = { top : 'bottom' , right : 'left' , bottom : 'top' , left : 'right' , } ; return oppositeSides [ side ] ; } /**
 * Creates a new rect (`ClientRect`) based on a given one but contracted by
 * a given amout on each side.
 */ function getContractedRect ( rect , amount ) { return DOMRect . fromRect ( { width : rect . width - amount * 2 , height : rect . height - amount * 2 , x : rect . left + amount , y : rect . top + amount , } ) ; } /**
 * Gets collisions for each side of a rect (top, right, bottom, left)
 */ function getCollisions ( /** The rect to test collisions against */ rect , /** The rect which represents the boundaries for collision checks */ collisionBoundariesRect ) { return { top : rect . top < collisionBoundariesRect . top , right : rect . right > collisionBoundariesRect . right , bottom : rect . bottom > collisionBoundariesRect . bottom , left : rect . left < collisionBoundariesRect . left , } ; } type Collisions = ReturnType < typeof getCollisions > ; export { getPlacementData , SIDE_OPTIONS , ALIGN_OPTIONS } ; export type { Side , Align } ;