license import { MDCFoundation } from '@material/base/foundation' ; import { MDCTabDimensions , MDCTabInteractionEvent } from '@material/tab/types' ; import { MDCTabBarAdapter } from './adapter' ; import { numbers , strings } from './constants' ; const ACCEPTABLE_KEYS = new Set < string > ( ) ; ACCEPTABLE_KEYS . add ( strings . ARROW_LEFT_KEY ) ; ACCEPTABLE_KEYS . add ( strings . ARROW_RIGHT_KEY ) ; ACCEPTABLE_KEYS . add ( strings . END_KEY ) ; ACCEPTABLE_KEYS . add ( strings . HOME_KEY ) ; ACCEPTABLE_KEYS . add ( strings . ENTER_KEY ) ; ACCEPTABLE_KEYS . add ( strings . SPACE_KEY ) ; const KEYCODE_MAP = new Map < number , string > ( ) ; KEYCODE_MAP . set ( numbers . ARROW_LEFT_KEYCODE , strings . ARROW_LEFT_KEY ) ; KEYCODE_MAP . set ( numbers . ARROW_RIGHT_KEYCODE , strings . ARROW_RIGHT_KEY ) ; KEYCODE_MAP . set ( numbers . END_KEYCODE , strings . END_KEY ) ; KEYCODE_MAP . set ( numbers . HOME_KEYCODE , strings . HOME_KEY ) ; KEYCODE_MAP . set ( numbers . ENTER_KEYCODE , strings . ENTER_KEY ) ; KEYCODE_MAP . set ( numbers . SPACE_KEYCODE , strings . SPACE_KEY ) ; export class MDCTabBarFoundation extends MDCFoundation < MDCTabBarAdapter > { static override get strings ( ) { return strings ; } static override get numbers ( ) { return numbers ; } static override get defaultAdapter ( ) : MDCTabBarAdapter { return { scrollTo : ( ) => undefined , incrementScroll : ( ) => undefined , getScrollPosition : ( ) => 0 , getScrollContentWidth : ( ) => 0 , getOffsetWidth : ( ) => 0 , isRTL : ( ) => false , setActiveTab : ( ) => undefined , activateTabAtIndex : ( ) => undefined , deactivateTabAtIndex : ( ) => undefined , focusTabAtIndex : ( ) => undefined , getTabIndicatorClientRectAtIndex : ( ) => ( { top : 0 , right : 0 , bottom : 0 , left : 0 , width : 0 , height : 0 } as any ) , getTabDimensionsAtIndex : ( ) => ( { rootLeft : 0 , rootRight : 0 , contentLeft : 0 , contentRight : 0 } ) , getPreviousActiveTabIndex : ( ) => - 1 , getFocusedTabIndex : ( ) => - 1 , getIndexOfTabById : ( ) => - 1 , getTabListLength : ( ) => 0 , notifyTabActivated : ( ) => undefined , } ; } private useAutomaticActivation = false ; constructor ( adapter ? ) { super ( { ... MDCTabBarFoundation . defaultAdapter , ... adapter } ) ; } /**
     * Switches between automatic and manual activation modes.
     * See https://www.w3.org/TR/wai-aria-practices/#tabpanel for examples.
     */ setUseAutomaticActivation ( useAutomaticActivation ) { this . useAutomaticActivation = useAutomaticActivation ; } activateTab ( index ) { const previousActiveIndex = this . adapter . getPreviousActiveTabIndex ( ) ; if ( ! this . indexIsInRange ( index ) || index === previousActiveIndex ) { return ; } let previousClientRect ; if ( previousActiveIndex !== - 1 ) { this . adapter . deactivateTabAtIndex ( previousActiveIndex ) ; previousClientRect = this . adapter . getTabIndicatorClientRectAtIndex ( previousActiveIndex ) ; } this . adapter . activateTabAtIndex ( index , previousClientRect ) ; this . scrollIntoView ( index ) ; this . adapter . notifyTabActivated ( index ) ; } handleKeyDown ( evt ) { const key = this . getKeyFromEvent ( evt ) ; if ( key === undefined ) { return ; } if ( ! this . isActivationKey ( key ) ) { evt . preventDefault ( ) ; } if ( this . useAutomaticActivation ) { if ( this . isActivationKey ( key ) ) { return ; } const index = this . determineTargetFromKey ( this . adapter . getPreviousActiveTabIndex ( ) , key ) ; this . adapter . setActiveTab ( index ) ; this . scrollIntoView ( index ) ; } else { const focusedTabIndex = this . adapter . getFocusedTabIndex ( ) ; if ( this . isActivationKey ( key ) ) { this . adapter . setActiveTab ( focusedTabIndex ) ; } else { const index = this . determineTargetFromKey ( focusedTabIndex , key ) ; this . adapter . focusTabAtIndex ( index ) ; this . scrollIntoView ( index ) ; } } } /**
     * Handles the MDCTab:interacted event
     */ handleTabInteraction ( evt ) { this . adapter . setActiveTab ( this . adapter . getIndexOfTabById ( evt . detail . tabId ) ) ; } param index scrollIntoView ( index ) { if ( ! this . indexIsInRange ( index ) ) { return ; } if ( index === 0 ) { this . adapter . scrollTo ( 0 ) ; return ; } if ( index === this . adapter . getTabListLength ( ) - 1 ) { this . adapter . scrollTo ( this . adapter . getScrollContentWidth ( ) ) ; return ; } if ( this . isRTL ( ) ) { this . scrollIntoViewImplRTL ( index ) ; return ; } this . scrollIntoViewImpl ( index ) ; } param origin param key private determineTargetFromKey ( origin , key ) { const isRTL = this . isRTL ( ) ; const maxIndex = this . adapter . getTabListLength ( ) - 1 ; const shouldGoToEnd = key === strings . END_KEY ; const shouldDecrement = key === strings . ARROW_LEFT_KEY && ! isRTL || key === strings . ARROW_RIGHT_KEY && isRTL ; const shouldIncrement = key === strings . ARROW_RIGHT_KEY && ! isRTL || key === strings . ARROW_LEFT_KEY && isRTL ; let index = origin ; if ( shouldGoToEnd ) { index = maxIndex ; } else if ( shouldDecrement ) { index -= 1 ; } else if ( shouldIncrement ) { index += 1 ; } else { index = 0 ; } if ( index < 0 ) { index = maxIndex ; } else if ( index > maxIndex ) { index = 0 ; } return index ; } param index param nextIndex param scrollPosition param barWidth private calculateScrollIncrement ( index , nextIndex , scrollPosition , barWidth ) { const nextTabDimensions = this . adapter . getTabDimensionsAtIndex ( nextIndex ) ; const relativeContentLeft = nextTabDimensions . contentLeft - scrollPosition - barWidth ; const relativeContentRight = nextTabDimensions . contentRight - scrollPosition ; const leftIncrement = relativeContentRight - numbers . EXTRA_SCROLL_AMOUNT ; const rightIncrement = relativeContentLeft + numbers . EXTRA_SCROLL_AMOUNT ; if ( nextIndex < index ) { return Math . min ( leftIncrement , 0 ) ; } return Math . max ( rightIncrement , 0 ) ; } param index param nextIndex param scrollPosition param barWidth param scrollContentWidth private calculateScrollIncrementRTL ( index , nextIndex , scrollPosition , barWidth , scrollContentWidth ) { const nextTabDimensions = this . adapter . getTabDimensionsAtIndex ( nextIndex ) ; const relativeContentLeft = scrollContentWidth - nextTabDimensions . contentLeft - scrollPosition ; const relativeContentRight = scrollContentWidth - nextTabDimensions . contentRight - scrollPosition - barWidth ; const leftIncrement = relativeContentRight + numbers . EXTRA_SCROLL_AMOUNT ; const rightIncrement = relativeContentLeft - numbers . EXTRA_SCROLL_AMOUNT ; if ( nextIndex > index ) { return Math . max ( leftIncrement , 0 ) ; } return Math . min ( rightIncrement , 0 ) ; } param index param tabDimensions param scrollPosition param barWidth private findAdjacentTabIndexClosestToEdge ( index , tabDimensions , scrollPosition , barWidth ) { /**
         * Tabs are laid out in the Tab Scroller like this:
         *
         *    Scroll Position
         *    +---+
         *    |   |   Bar Width
         *    |   +-----------------------------------+
         *    |   |                                   |
         *    |   V                                   V
         *    |   +-----------------------------------+
         *    V   |             Tab Scroller          |
         *    +------------+--------------+-------------------+
         *    |    Tab     |      Tab     |        Tab        |
         *    +------------+--------------+-------------------+
         *        |                                   |
         *        +-----------------------------------+
         *
         * To determine the next adjacent index, we look at the Tab root left and
         * Tab root right, both relative to the scroll position. If the Tab root
         * left is less than 0, then we know it's out of view to the left. If the
         * Tab root right minus the bar width is greater than 0, we know the Tab is
         * out of view to the right. From there, we either increment or decrement
         * the index.
         */ const relativeRootLeft = tabDimensions . rootLeft - scrollPosition ; const relativeRootRight = tabDimensions . rootRight - scrollPosition - barWidth ; const relativeRootDelta = relativeRootLeft + relativeRootRight ; const leftEdgeIsCloser = relativeRootLeft < 0 || relativeRootDelta < 0 ; const rightEdgeIsCloser = relativeRootRight > 0 || relativeRootDelta > 0 ; if ( leftEdgeIsCloser ) { return index - 1 ; } if ( rightEdgeIsCloser ) { return index + 1 ; } return - 1 ; } param index param tabDimensions param scrollPosition param barWidth param scrollContentWidth private findAdjacentTabIndexClosestToEdgeRTL ( index , tabDimensions , scrollPosition , barWidth , scrollContentWidth ) { const rootLeft = scrollContentWidth - tabDimensions . rootLeft - barWidth - scrollPosition ; const rootRight = scrollContentWidth - tabDimensions . rootRight - scrollPosition ; const rootDelta = rootLeft + rootRight ; const leftEdgeIsCloser = rootLeft > 0 || rootDelta > 0 ; const rightEdgeIsCloser = rootRight < 0 || rootDelta < 0 ; if ( leftEdgeIsCloser ) { return index + 1 ; } if ( rightEdgeIsCloser ) { return index - 1 ; } return - 1 ; } param evt private getKeyFromEvent ( evt ) { if ( ACCEPTABLE_KEYS . has ( evt . key ) ) { return evt . key ; } return KEYCODE_MAP . get ( evt . keyCode ) ! ; } private isActivationKey ( key ) { return key === strings . SPACE_KEY || key === strings . ENTER_KEY ; } param index private indexIsInRange ( index ) { return index >= 0 && index < this . adapter . getTabListLength ( ) ; } /**
     * Returns the view's RTL property
     */ private isRTL ( ) { return this . adapter . isRTL ( ) ; } param index private scrollIntoViewImpl ( index ) { const scrollPosition = this . adapter . getScrollPosition ( ) ; const barWidth = this . adapter . getOffsetWidth ( ) ; const tabDimensions = this . adapter . getTabDimensionsAtIndex ( index ) ; const nextIndex = this . findAdjacentTabIndexClosestToEdge ( index , tabDimensions , scrollPosition , barWidth ) ; if ( ! this . indexIsInRange ( nextIndex ) ) { return ; } const scrollIncrement = this . calculateScrollIncrement ( index , nextIndex , scrollPosition , barWidth ) ; this . adapter . incrementScroll ( scrollIncrement ) ; } param index private scrollIntoViewImplRTL ( index ) { const scrollPosition = this . adapter . getScrollPosition ( ) ; const barWidth = this . adapter . getOffsetWidth ( ) ; const tabDimensions = this . adapter . getTabDimensionsAtIndex ( index ) ; const scrollWidth = this . adapter . getScrollContentWidth ( ) ; const nextIndex = this . findAdjacentTabIndexClosestToEdgeRTL ( index , tabDimensions , scrollPosition , barWidth , scrollWidth ) ; if ( ! this . indexIsInRange ( nextIndex ) ) { return ; } const scrollIncrement = this . calculateScrollIncrementRTL ( index , nextIndex , scrollPosition , barWidth , scrollWidth ) ; this . adapter . incrementScroll ( scrollIncrement ) ; } } export default MDCTabBarFoundation ;