license import { MDCFoundation } from '@material/base/foundation' ; import { MDCTabScrollerAdapter } from './adapter' ; import { cssClasses , strings } from './constants' ; import { MDCTabScrollerRTLDefault } from './rtl-default-scroller' ; import { MDCTabScrollerRTLNegative } from './rtl-negative-scroller' ; import { MDCTabScrollerRTLReverse } from './rtl-reverse-scroller' ; import { MDCTabScrollerRTL } from './rtl-scroller' ; import { MDCTabScrollerAnimation , MDCTabScrollerHorizontalEdges } from './types' ; export class MDCTabScrollerFoundation extends MDCFoundation < MDCTabScrollerAdapter > { static override get cssClasses ( ) { return cssClasses ; } static override get strings ( ) { return strings ; } static override get defaultAdapter ( ) : MDCTabScrollerAdapter { return { eventTargetMatchesSelector : ( ) => false , addClass : ( ) => undefined , removeClass : ( ) => undefined , addScrollAreaClass : ( ) => undefined , setScrollAreaStyleProperty : ( ) => undefined , setScrollContentStyleProperty : ( ) => undefined , getScrollContentStyleValue : ( ) => '' , setScrollAreaScrollLeft : ( ) => undefined , getScrollAreaScrollLeft : ( ) => 0 , getScrollContentOffsetWidth : ( ) => 0 , getScrollAreaOffsetWidth : ( ) => 0 , computeScrollAreaClientRect : ( ) => ( { top : 0 , right : 0 , bottom : 0 , left : 0 , width : 0 , height : 0 } as any ) , computeScrollContentClientRect : ( ) => ( { top : 0 , right : 0 , bottom : 0 , left : 0 , width : 0 , height : 0 } as any ) , computeHorizontalScrollbarHeight : ( ) => 0 , } ; } /**
     * Controls whether we should handle the transitionend and interaction events during the animation.
     */ private isAnimating = false ; /**
     * The MDCTabScrollerRTL instance varies per browser and allows us to encapsulate the peculiar browser behavior
     * of RTL scrolling in it's own class.
     */ private rtlScrollerInstance ? : MDCTabScrollerRTL ; constructor ( adapter ? ) { super ( { ... MDCTabScrollerFoundation . defaultAdapter , ... adapter } ) ; } override init ( ) { const horizontalScrollbarHeight = this . adapter . computeHorizontalScrollbarHeight ( ) ; this . adapter . setScrollAreaStyleProperty ( 'margin-bottom' , - horizontalScrollbarHeight + 'px' ) ; this . adapter . addScrollAreaClass ( MDCTabScrollerFoundation . cssClasses . SCROLL_AREA_SCROLL ) ; } /**
     * Computes the current visual scroll position
     */ getScrollPosition ( ) { if ( this . isRTL ( ) ) { return this . computeCurrentScrollPositionRTL ( ) ; } const currentTranslateX = this . calculateCurrentTranslateX ( ) ; const scrollLeft = this . adapter . getScrollAreaScrollLeft ( ) ; return scrollLeft - currentTranslateX ; } /**
     * Handles interaction events that occur during transition
     */ handleInteraction ( ) { if ( ! this . isAnimating ) { return ; } this . stopScrollAnimation ( ) ; } /**
     * Handles the transitionend event
     */ handleTransitionEnd ( evt ) { const evtTarget = evt . target as Element ; if ( ! this . isAnimating || ! this . adapter . eventTargetMatchesSelector ( evtTarget , MDCTabScrollerFoundation . strings . CONTENT_SELECTOR ) ) { return ; } this . isAnimating = false ; this . adapter . removeClass ( MDCTabScrollerFoundation . cssClasses . ANIMATING ) ; } param scrollXIncrement incrementScroll ( scrollXIncrement ) { if ( scrollXIncrement === 0 ) { return ; } this . animate ( this . getIncrementScrollOperation ( scrollXIncrement ) ) ; } param scrollXIncrement incrementScrollImmediate ( scrollXIncrement ) { if ( scrollXIncrement === 0 ) { return ; } const operation = this . getIncrementScrollOperation ( scrollXIncrement ) ; if ( operation . scrollDelta === 0 ) { return ; } this . stopScrollAnimation ( ) ; this . adapter . setScrollAreaScrollLeft ( operation . finalScrollPosition ) ; } /**
     * Scrolls to the given scrollX value
     */ scrollTo ( scrollX ) { if ( this . isRTL ( ) ) { this . scrollToImplRTL ( scrollX ) ; return ; } this . scrollToImpl ( scrollX ) ; } return Browser-specific  MDCTabScrollerRTL instance.
      getRTLScroller ( ) { if ( ! this . rtlScrollerInstance ) { this . rtlScrollerInstance = this . rtlScrollerFactory ( ) ; } return this . rtlScrollerInstance ; } return private calculateCurrentTranslateX ( ) { const transformValue = this . adapter . getScrollContentStyleValue ( 'transform' ) ; if ( transformValue === 'none' ) { return 0 ; } const match = /\((.+?)\)/ . exec ( transformValue ) ; if ( ! match ) { return 0 ; } const matrixParams = match [ 1 ] ; const [ a , b , c , d , tx , ty ] = matrixParams . split ( ',' ) ; return parseFloat ( tx ) ; } param scrollX private clampScrollValue ( scrollX ) { const edges = this . calculateScrollEdges ( ) ; return Math . min ( Math . max ( edges . left , scrollX ) , edges . right ) ; } private computeCurrentScrollPositionRTL ( ) { const translateX = this . calculateCurrentTranslateX ( ) ; return this . getRTLScroller ( ) . getScrollPositionRTL ( translateX ) ; } private calculateScrollEdges ( ) { const contentWidth = this . adapter . getScrollContentOffsetWidth ( ) ; const rootWidth = this . adapter . getScrollAreaOffsetWidth ( ) ; return { left : 0 , right : contentWidth - rootWidth , } ; } param scrollX private scrollToImpl ( scrollX ) { const currentScrollX = this . getScrollPosition ( ) ; const safeScrollX = this . clampScrollValue ( scrollX ) ; const scrollDelta = safeScrollX - currentScrollX ; this . animate ( { finalScrollPosition : safeScrollX , scrollDelta , } ) ; } param scrollX private scrollToImplRTL ( scrollX ) { const animation = this . getRTLScroller ( ) . scrollToRTL ( scrollX ) ; this . animate ( animation ) ; } param scrollX return private getIncrementScrollOperation ( scrollX ) { if ( this . isRTL ( ) ) { return this . getRTLScroller ( ) . incrementScrollRTL ( scrollX ) ; } const currentScrollX = this . getScrollPosition ( ) ; const targetScrollX = scrollX + currentScrollX ; const safeScrollX = this . clampScrollValue ( targetScrollX ) ; const scrollDelta = safeScrollX - currentScrollX ; return { finalScrollPosition : safeScrollX , scrollDelta , } ; } param animation private animate ( animation ) { if ( animation . scrollDelta === 0 ) { return ; } this . stopScrollAnimation ( ) ; this . adapter . setScrollAreaScrollLeft ( animation . finalScrollPosition ) ; this . adapter . setScrollContentStyleProperty ( 'transform' , `translateX(${ animation . scrollDelta }px)` ) ; this . adapter . computeScrollAreaClientRect ( ) ; requestAnimationFrame ( ( ) => { this . adapter . addClass ( MDCTabScrollerFoundation . cssClasses . ANIMATING ) ; this . adapter . setScrollContentStyleProperty ( 'transform' , 'none' ) ; } ) ; this . isAnimating = true ; } /**
     * Stops scroll animation
     */ private stopScrollAnimation ( ) { this . isAnimating = false ; const currentScrollPosition = this . getAnimatingScrollPosition ( ) ; this . adapter . removeClass ( MDCTabScrollerFoundation . cssClasses . ANIMATING ) ; this . adapter . setScrollContentStyleProperty ( 'transform' , 'translateX(0px)' ) ; this . adapter . setScrollAreaScrollLeft ( currentScrollPosition ) ; } /**
     * Gets the current scroll position during animation
     */ private getAnimatingScrollPosition ( ) { const currentTranslateX = this . calculateCurrentTranslateX ( ) ; const scrollLeft = this . adapter . getScrollAreaScrollLeft ( ) ; if ( this . isRTL ( ) ) { return this . getRTLScroller ( ) . getAnimatingScrollPosition ( scrollLeft , currentTranslateX ) ; } return scrollLeft - currentTranslateX ; } /**
     * Determines the RTL Scroller to use
     */ private rtlScrollerFactory ( ) { const initialScrollLeft = this . adapter . getScrollAreaScrollLeft ( ) ; this . adapter . setScrollAreaScrollLeft ( initialScrollLeft - 1 ) ; const newScrollLeft = this . adapter . getScrollAreaScrollLeft ( ) ; if ( newScrollLeft < 0 ) { this . adapter . setScrollAreaScrollLeft ( initialScrollLeft ) ; return new MDCTabScrollerRTLNegative ( this . adapter ) ; } const rootClientRect = this . adapter . computeScrollAreaClientRect ( ) ; const contentClientRect = this . adapter . computeScrollContentClientRect ( ) ; const rightEdgeDelta = Math . round ( contentClientRect . right - rootClientRect . right ) ; this . adapter . setScrollAreaScrollLeft ( initialScrollLeft ) ; if ( rightEdgeDelta === newScrollLeft ) { return new MDCTabScrollerRTLReverse ( this . adapter ) ; } return new MDCTabScrollerRTLDefault ( this . adapter ) ; } private isRTL ( ) { return this . adapter . getScrollContentStyleValue ( 'direction' ) === 'rtl' ; } } export default MDCTabScrollerFoundation ;