import type { CallbackFunction } from '../types' ; const OVERFLOW_REG_EXP = /(auto|scroll)/ ; private param c param d param s param t function easeOutCubic ( c , d , s , t ) { return c * ( ( t = t / d - 1 ) * t * t + 1 ) + s ; } private function getScrollTop ( el ) { return isDocumentElement ( el ) ? window . pageYOffset : el . scrollTop ; } private function scrollTo ( el , top ) { isDocumentElement ( el ) ? window . scrollTo ( 0 , top ) : ( el . scrollTop = top ) ; } private function isDocumentElement ( el ) { return el === document . body || el === document . documentElement || el === window ; } private function isScrollableStyle ( { overflow , overflowX , overflowY } ) { return OVERFLOW_REG_EXP . test ( `${ overflow }${ overflowX }${ overflowY }` ) ; } private function getScrollParent ( el ) { let style = getComputedStyle ( el ) ; const docEl = document . documentElement ; const isParentAbs = style . position === 'absolute' ; if ( style . position === 'fixed' ) { return docEl ; } for ( let parent = el ; ( parent = parent ?. parentElement ) ; ) { style = getComputedStyle ( parent ) ; if ( ! ( isParentAbs && style . position === 'static' ) && isScrollableStyle ( style ) ) { return parent ; } } return docEl ; } private function smoothScrollTo ( el , to , duration = 300 , callback ? ) { let currentTime = 0 ; const start = getScrollTop ( el ) ; const change = to - start ; function scrollFn ( ) { currentTime += 5 ; const calcScrollTop = easeOutCubic ( change , duration , start , currentTime ) ; scrollTo ( el , calcScrollTop ) ; ( currentTime < duration ) ? requestAnimationFrame ( scrollFn ) : callback ?. ( ) ; } requestAnimationFrame ( scrollFn ) ; } /**
 * Calculates the top property value for the MenuWrapper <div />.
 * This property is only generated when the position of the menu is above the control.
 */ export const calculateMenuTop = ( menuHeight , menuEl , controlEl ) : string => { const menuHeightOrDefault = ( menuHeight > 0 || ! menuEl ) ? menuHeight : menuEl . getBoundingClientRect ( ) . height ; const controlHeight = controlEl ? controlEl . getBoundingClientRect ( ) . height : 0 ; const menuElStyle = menuEl && getComputedStyle ( menuEl ) ; const marginBottom = menuElStyle ? parseInt ( menuElStyle . marginBottom , 10 ) : 0 ; const marginTop = menuElStyle ? parseInt ( menuElStyle . marginTop , 10 ) : 0 ; const basePx = - Math . abs ( menuHeightOrDefault + controlHeight ) ; const adjustPx = marginBottom + marginTop ; return `calc(${ basePx }px + ${ adjustPx }px)` ; } ; export const menuFitsBelowControl = ( el ) : boolean => { if ( ! el ) return true ; const scrollParent = getScrollParent ( el ) ; const { top , height } = el . getBoundingClientRect ( ) ; const { height : scrollParentHeight } = scrollParent . getBoundingClientRect ( ) ; const spaceBelow = scrollParentHeight - getScrollTop ( scrollParent ) - top ; return spaceBelow >= height ; } ; /**
 * Calculate space around the control and menu to determine if an animated
 * scroll can performed to show the menu in full view. Also, execute a callback if defined.
 */ export const scrollMenuIntoViewOnOpen = ( menuEl , menuScrollDuration , scrollMenuIntoView , handleOnMenuOpen ) : void => { if ( ! menuEl ) { handleOnMenuOpen ( ) ; return ; } const { top , height , bottom } = menuEl . getBoundingClientRect ( ) ; const viewInner = window . innerHeight ; const viewSpaceBelow = viewInner - top ; if ( viewSpaceBelow >= height ) { handleOnMenuOpen ( ) ; return ; } const scrollParent = getScrollParent ( menuEl ) ; const scrollTop = getScrollTop ( scrollParent ) ; const spaceBelow = scrollParent . getBoundingClientRect ( ) . height - scrollTop - top ; const notEnoughSpaceBelow = spaceBelow < height ; if ( notEnoughSpaceBelow || ! scrollMenuIntoView ) { const condensedMenuHeight = notEnoughSpaceBelow ? spaceBelow : undefined ; handleOnMenuOpen ( condensedMenuHeight ) ; return ; } const marginBottomStyle = getComputedStyle ( menuEl ) . marginBottom ; const marginBottom = parseInt ( marginBottomStyle , 10 ) ; const scrollDown = bottom - viewInner + scrollTop + marginBottom ; smoothScrollTo ( scrollParent , scrollDown , menuScrollDuration , handleOnMenuOpen ) ; } ;