import { clearTimeout , setTimeout } from 'requestanimationframe-timer' ; param percent param elapsedTime param startValue param valueChange param duration returns export type ScrollchorEasingFunction = ( percent : number , elapsedTime : number , startValue : number , valueChange : number , duration : number ) => number ; export interface AnimateConfig { /**
     * Additional pixels to scroll relative to the target element (supports negative values, e.g.
     * for fixed position headers)
     */ offset : number ; /**
     * Length of the animation in milliseconds
     */ duration : number ; /**
     * Easing function to calculate the animation steps. Pass a function that matches the exported
     * interface for a custom easing.
     */ easing : ScrollchorEasingFunction ; } internal type ScrollAnimator = ( id : string , targetId : string , animate : AnimateConfig ) => Promise < string | void > ; internal export const animateScroll = ( ( ) : ScrollAnimator => { let timeoutId ; let resolvePrevious ; return ( id , targetId , animate ) : ReturnType < ScrollAnimator > => { let targetElement ; if ( targetId !== '' ) { try { const el = document . getElementById ( targetId ) ; if ( el !== null ) { targetElement = el ; } } catch ( e ) { console . error ( `Failed to get element by id ${ targetId }, falling back to default` ) ; } } function getScrollTop ( ) { return targetElement ? targetElement . scrollTop : document . documentElement . scrollTop || document . body . scrollTop ; } function setScrollTop ( position ) { if ( targetElement ) { targetElement . scrollTop = position ; } else { document . documentElement . scrollTop = position ; document . body . scrollTop = position ; } } function getOffsetTop < T extends Element > ( element ) { const parentOffsetTop = targetElement ? targetElement . getBoundingClientRect ( ) . top : 0 ; return element . getBoundingClientRect ( ) . top - parentOffsetTop + getScrollTop ( ) ; } return new Promise < string | void > ( ( resolve , reject ) => { const element = id ? document . getElementById ( id ) : document . body ; if ( ! element ) { reject ( new Error ( `Cannot find element: #${ id }` ) ) ; return ; } const { offset , duration , easing , } = animate ; const startTime = Date . now ( ) ; const start = getScrollTop ( ) ; const end = getOffsetTop ( element ) + offset ; function animateFn ( ) { const currentTime = Date . now ( ) ; const remaining = Math . max ( 0 , startTime + duration - currentTime ) ; const percent = 1 - ( remaining / duration || 0 ) ; const eased = easing ( percent , duration * percent , 0 , 1 , duration ) ; const now = ( end - start ) * eased + start ; setScrollTop ( now ) ; if ( percent < 1 ) { const increment = 20 ; timeoutId = setTimeout ( animateFn , increment ) ; } else { timeoutId = undefined ; resolve ( id ) ; } } if ( timeoutId ) { clearTimeout ( timeoutId ) ; resolvePrevious ( ) ; } resolvePrevious = resolve ; animateFn ( ) ; } ) ; } ; } ) ( ) ; internal export function updateHistory ( id ) { const hashId = `#${ id }` ; if ( typeof window . history . pushState === 'function' ) { window . history . pushState ( { } , '' , hashId ) ; } else { window . location . hash = hashId ; } } internal export function normalizeId ( id ? ) { if ( typeof id === 'string' ) { return id . replace ( /^#/ , '' ) ; } return '' ; }