import { Inject , Injectable , isDevMode } from '@angular/core' ; import { PageScrollConfig } from '../types/page-scroll.config' ; import { InterruptReporter , PageScrollInstance , PageScrollOptions } from '../page-scroll-instance' ; import { defaultPageScrollConfig , NGXPS_CONFIG } from './config.provider' ; @ Injectable ( { providedIn : 'root' , } ) export class PageScrollService { private readonly config : PageScrollConfig ; private runningInstances : PageScrollInstance [ ] = [ ] ; private onInterrupted : InterruptReporter = { report : ( event , pageScrollInstance ) : void => { if ( ! pageScrollInstance . pageScrollOptions . interruptible ) { return ; } let shouldStop = true ; if ( event . type === 'keyup' ) { if ( this . config . interruptKeys . indexOf ( ( event as KeyboardEvent ) . key ) === - 1 ) { shouldStop = false ; } } else if ( event . type === 'mousedown' ) { if ( ! pageScrollInstance . pageScrollOptions . scrollViews . some ( scrollingView => scrollingView . contains ( event . target as Node ) ) ) { shouldStop = false ; } } if ( shouldStop ) { this . stopAll ( pageScrollInstance . pageScrollOptions . namespace ) ; } } , } ; private stopInternal ( interrupted , pageScrollInstance ) { const index = this . runningInstances . indexOf ( pageScrollInstance ) ; if ( index >= 0 ) { this . runningInstances . splice ( index , 1 ) ; } if ( pageScrollInstance . interruptListenersAttached ) { pageScrollInstance . detachInterruptListeners ( ) ; } if ( pageScrollInstance . timer ) { clearInterval ( pageScrollInstance . timer ) ; pageScrollInstance . timer = undefined ; pageScrollInstance . fireEvent ( ! interrupted ) ; return true ; } return false ; } public create ( options ) { return new PageScrollInstance ( { ... this . config , ... options } as PageScrollOptions ) ; } /**
     * Start a scroll animation. All properties of the animation are stored in the given  PageScrollInstance object.
     *
     * This is the core functionality of the whole library.
      public start ( pageScrollInstance ) { pageScrollInstance . pageScrollOptions = { ... this . config , ... pageScrollInstance . pageScrollOptions } as PageScrollOptions ; this . stopAll ( pageScrollInstance . pageScrollOptions . namespace ) ; if ( pageScrollInstance . pageScrollOptions . scrollViews === null || pageScrollInstance . pageScrollOptions . scrollViews . length === 0 ) { if ( this . config . _logLevel >= 2 || ( this . config . _logLevel >= 1 && isDevMode ( ) ) ) { console . warn ( 'No scrollViews specified, thus ngx-page-scroll does not know which DOM elements to scroll' ) ; } return ; } let startScrollPositionFound = false ; let scrollRange = pageScrollInstance . getScrollClientPropertyValue ( pageScrollInstance . pageScrollOptions . scrollViews [ 0 ] ) ; pageScrollInstance . startScrollPosition = 0 ; pageScrollInstance . pageScrollOptions . scrollViews . forEach ( scrollingView => { if ( scrollingView === undefined || scrollingView === null ) { return ; } const scrollPosition = pageScrollInstance . getScrollPropertyValue ( scrollingView ) ; if ( ! startScrollPositionFound && scrollPosition ) { pageScrollInstance . startScrollPosition = scrollPosition ; startScrollPositionFound = true ; scrollRange = pageScrollInstance . getScrollClientPropertyValue ( scrollingView ) ; } } ) ; const pageScrollOffset = pageScrollInstance . getCurrentOffset ( ) ; const scrollTargetPosition = pageScrollInstance . extractScrollTargetPosition ( ) ; pageScrollInstance . targetScrollPosition = Math . round ( ( pageScrollInstance . pageScrollOptions . verticalScrolling ? scrollTargetPosition . top : scrollTargetPosition . left ) - pageScrollOffset ) ; pageScrollInstance . distanceToScroll = pageScrollInstance . targetScrollPosition - pageScrollInstance . startScrollPosition ; if ( isNaN ( pageScrollInstance . distanceToScroll ) ) { if ( this . config . _logLevel >= 2 || ( this . config . _logLevel >= 1 && isDevMode ( ) ) ) { console . log ( 'Scrolling not possible, as we can\'t find the specified target' ) ; } pageScrollInstance . fireEvent ( false ) ; return ; } const allReadyAtDestination = Math . abs ( pageScrollInstance . distanceToScroll ) < pageScrollInstance . pageScrollOptions . _minScrollDistance ; pageScrollInstance . executionDuration = pageScrollInstance . pageScrollOptions . duration ; if ( ( pageScrollInstance . pageScrollOptions . speed !== undefined && pageScrollInstance . pageScrollOptions . speed !== null ) && ( pageScrollInstance . pageScrollOptions . duration === undefined || pageScrollInstance . pageScrollOptions . duration === null ) ) { pageScrollInstance . executionDuration = Math . abs ( pageScrollInstance . distanceToScroll ) / pageScrollInstance . pageScrollOptions . speed * 1000 ; } const tooShortInterval = pageScrollInstance . executionDuration <= pageScrollInstance . pageScrollOptions . _interval ; if ( allReadyAtDestination || tooShortInterval ) { if ( this . config . _logLevel >= 2 || ( this . config . _logLevel >= 1 && isDevMode ( ) ) ) { if ( allReadyAtDestination ) { console . log ( 'Scrolling not possible, as we can\'t get any closer to the destination' ) ; } else { console . log ( 'Scroll duration shorter that interval length, jumping to target' ) ; } } pageScrollInstance . setScrollPosition ( pageScrollInstance . targetScrollPosition ) ; pageScrollInstance . fireEvent ( true ) ; return ; } if ( ! pageScrollInstance . pageScrollOptions . scrollInView ) { const alreadyInView = pageScrollInstance . targetScrollPosition > pageScrollInstance . startScrollPosition && pageScrollInstance . targetScrollPosition <= pageScrollInstance . startScrollPosition + scrollRange ; if ( alreadyInView ) { if ( this . config . _logLevel >= 2 || ( this . config . _logLevel >= 1 && isDevMode ( ) ) ) { console . log ( 'Not scrolling, as target already in view' ) ; } pageScrollInstance . fireEvent ( true ) ; return ; } } if ( pageScrollInstance . pageScrollOptions . interruptible ) { pageScrollInstance . attachInterruptListeners ( this . onInterrupted ) ; } pageScrollInstance . startTime = new Date ( ) . getTime ( ) ; pageScrollInstance . endTime = pageScrollInstance . startTime + pageScrollInstance . executionDuration ; pageScrollInstance . timer = setInterval ( ( instance ) => { const currentTime = new Date ( ) . getTime ( ) ; let newScrollPosition ; let stopNow = false ; if ( instance . endTime <= currentTime ) { newScrollPosition = instance . targetScrollPosition ; stopNow = true ; } else { newScrollPosition = Math . round ( instance . pageScrollOptions . easingLogic ( currentTime - instance . startTime , instance . startScrollPosition , instance . distanceToScroll , instance . executionDuration ) ) ; } if ( this . config . _logLevel >= 5 && isDevMode ( ) ) { console . warn ( 'Scroll Position: ' + newScrollPosition ) ; } if ( ! instance . setScrollPosition ( newScrollPosition ) ) { stopNow = true ; } if ( stopNow ) { this . stopInternal ( false , instance ) ; } } , this . config . _interval , pageScrollInstance ) ; this . runningInstances . push ( pageScrollInstance ) ; } public scroll ( options ) { this . start ( this . create ( options ) ) ; } /**
     * Stop all running scroll animations. Optionally limit to stop only the ones of specific namespace.
     */ public stopAll ( namespace ? ) { if ( this . runningInstances . length > 0 ) { let stoppedSome = false ; for ( let i = 0 ; i < this . runningInstances . length ; ++ i ) { const pageScrollInstance = this . runningInstances [ i ] ; if ( ! namespace || pageScrollInstance . pageScrollOptions . namespace === namespace ) { stoppedSome = true ; this . stopInternal ( true , pageScrollInstance ) ; i -- ; } } return stoppedSome ; } return false ; } public stop ( pageScrollInstance ) { return this . stopInternal ( true , pageScrollInstance ) ; } constructor ( @ Inject ( NGXPS_CONFIG ) customConfig ) { this . config = { ... defaultPageScrollConfig , ... customConfig } ; } }