license import { AnimationFrame } from '@material/animation/animationframe' ; import { MDCFoundation } from '@material/base/foundation' ; import { KEY } from '@material/dom/keyboard' ; import { MDCChipActionFocusBehavior , MDCChipActionInteractionTrigger , MDCChipActionType } from '../action/constants' ; import { MDCChipActionInteractionEventDetail } from '../action/types' ; import { MDCChipAdapter } from './adapter' ; import { MDCChipAnimation , MDCChipAttributes , MDCChipCssClasses , MDCChipEvents } from './constants' ; import { ActionInteractionEvent , ActionNavigationEvent , MDCChipAnimationEventDetail , MDCChipInteractionEventDetail , MDCChipNavigationEventDetail } from './types' ; interface Navigation { from : MDCChipActionType ; to : MDCChipActionType ; } enum Direction { UNSPECIFIED , LEFT , RIGHT } enum AnimationKeys { SELECTION = 'selection' , EXIT = 'exit' } /**
 * MDCChipFoundation provides a foundation for all chips.
 */ export class MDCChipFoundation extends MDCFoundation < MDCChipAdapter > { static override get defaultAdapter ( ) : MDCChipAdapter { return { addClass : ( ) => undefined , emitEvent : ( ) => undefined , getActions : ( ) => [ ] , getAttribute : ( ) => null , getElementID : ( ) => '' , getOffsetWidth : ( ) => 0 , hasClass : ( ) => false , isActionDisabled : ( ) => false , isActionFocusable : ( ) => false , isActionSelectable : ( ) => false , isActionSelected : ( ) => false , isRTL : ( ) => false , removeClass : ( ) => undefined , setActionDisabled : ( ) => undefined , setActionFocus : ( ) => undefined , setActionSelected : ( ) => undefined , setStyleProperty : ( ) => undefined , } ; } private readonly animFrame : AnimationFrame ; constructor ( adapter ? ) { super ( { ... MDCChipFoundation . defaultAdapter , ... adapter } ) ; this . animFrame = new AnimationFrame ( ) ; } override destroy ( ) { this . animFrame . cancelAll ( ) ; } getElementID ( ) { return this . adapter . getElementID ( ) ; } setDisabled ( isDisabled ) { const actions = this . getActions ( ) ; for ( const action of actions ) { this . adapter . setActionDisabled ( action , isDisabled ) ; } if ( isDisabled ) { this . adapter . addClass ( MDCChipCssClasses . DISABLED ) ; } else { this . adapter . removeClass ( MDCChipCssClasses . DISABLED ) ; } } isDisabled ( ) { const actions = this . getActions ( ) ; for ( const action of actions ) { if ( this . adapter . isActionDisabled ( action ) ) { return true ; } } return false ; } getActions ( ) { return this . adapter . getActions ( ) ; } isActionFocusable ( action ) { return this . adapter . isActionFocusable ( action ) ; } isActionSelectable ( action ) { return this . adapter . isActionSelectable ( action ) ; } isActionSelected ( action ) { return this . adapter . isActionSelected ( action ) ; } setActionFocus ( action , focus ) { this . adapter . setActionFocus ( action , focus ) ; } setActionSelected ( action , isSelected ) { this . adapter . setActionSelected ( action , isSelected ) ; this . animateSelection ( isSelected ) ; } startAnimation ( animation ) { if ( animation === MDCChipAnimation . ENTER ) { this . adapter . addClass ( MDCChipCssClasses . ENTER ) ; return ; } if ( animation === MDCChipAnimation . EXIT ) { this . adapter . addClass ( MDCChipCssClasses . EXIT ) ; return ; } } handleAnimationEnd ( event ) { const { animationName } = event ; if ( animationName === MDCChipAnimation . ENTER ) { this . adapter . removeClass ( MDCChipCssClasses . ENTER ) ; this . adapter . emitEvent < MDCChipAnimationEventDetail > ( MDCChipEvents . ANIMATION , { chipID : this . getElementID ( ) , animation : MDCChipAnimation . ENTER , addedAnnouncement : this . getAddedAnnouncement ( ) , isComplete : true , } ) ; return ; } if ( animationName === MDCChipAnimation . EXIT ) { this . adapter . removeClass ( MDCChipCssClasses . EXIT ) ; this . adapter . addClass ( MDCChipCssClasses . HIDDEN ) ; const width = this . adapter . getOffsetWidth ( ) ; this . adapter . setStyleProperty ( 'width' , `${ width }px` ) ; this . animFrame . request ( AnimationKeys . EXIT , ( ) => { this . animFrame . request ( AnimationKeys . EXIT , ( ) => { this . adapter . setStyleProperty ( 'width' , '0' ) ; } ) ; } ) ; } } handleTransitionEnd ( ) { if ( ! this . adapter . hasClass ( MDCChipCssClasses . HIDDEN ) ) return ; this . adapter . emitEvent < MDCChipAnimationEventDetail > ( MDCChipEvents . ANIMATION , { chipID : this . getElementID ( ) , animation : MDCChipAnimation . EXIT , removedAnnouncement : this . getRemovedAnnouncement ( ) , isComplete : true , } ) ; } handleActionInteraction ( { detail } ) { const { source , actionID } = detail ; const isSelectable = this . adapter . isActionSelectable ( source ) ; const isSelected = this . adapter . isActionSelected ( source ) ; this . adapter . emitEvent < MDCChipInteractionEventDetail > ( MDCChipEvents . INTERACTION , { chipID : this . getElementID ( ) , shouldRemove : this . shouldRemove ( detail ) , actionID , isSelectable , isSelected , source , } ) ; } handleActionNavigation ( { detail } ) { const { source , key } = detail ; const isRTL = this . adapter . isRTL ( ) ; const isTrailingActionFocusable = this . adapter . isActionFocusable ( MDCChipActionType . TRAILING ) ; const isPrimaryActionFocusable = this . adapter . isActionFocusable ( MDCChipActionType . PRIMARY ) ; const dir = this . directionFromKey ( key , isRTL ) ; const shouldNavigateToTrailing = source === MDCChipActionType . PRIMARY && dir === Direction . RIGHT && isTrailingActionFocusable ; const shouldNavigateToPrimary = source === MDCChipActionType . TRAILING && dir === Direction . LEFT && isPrimaryActionFocusable ; if ( shouldNavigateToTrailing ) { this . navigateActions ( { from : source , to : MDCChipActionType . TRAILING } ) ; return ; } if ( shouldNavigateToPrimary ) { this . navigateActions ( { from : source , to : MDCChipActionType . PRIMARY } ) ; return ; } this . adapter . emitEvent < MDCChipNavigationEventDetail > ( MDCChipEvents . NAVIGATION , { chipID : this . getElementID ( ) , isRTL , source , key , } ) ; } private directionFromKey ( key , isRTL ) { const isLeftKey = key === KEY . ARROW_LEFT ; const isRightKey = key === KEY . ARROW_RIGHT ; if ( ! isRTL && isLeftKey || isRTL && isRightKey ) { return Direction . LEFT ; } if ( ! isRTL && isRightKey || isRTL && isLeftKey ) { return Direction . RIGHT ; } return Direction . UNSPECIFIED ; } private navigateActions ( nav ) { this . adapter . setActionFocus ( nav . from , MDCChipActionFocusBehavior . NOT_FOCUSABLE ) ; this . adapter . setActionFocus ( nav . to , MDCChipActionFocusBehavior . FOCUSABLE_AND_FOCUSED ) ; } private shouldRemove ( { source , trigger } ) { if ( trigger === MDCChipActionInteractionTrigger . BACKSPACE_KEY || trigger === MDCChipActionInteractionTrigger . DELETE_KEY ) { return true ; } return source === MDCChipActionType . TRAILING ; } private getRemovedAnnouncement ( ) { const msg = this . adapter . getAttribute ( MDCChipAttributes . DATA_REMOVED_ANNOUNCEMENT ) ; return msg || undefined ; } private getAddedAnnouncement ( ) { const msg = this . adapter . getAttribute ( MDCChipAttributes . DATA_ADDED_ANNOUNCEMENT ) ; return msg || undefined ; } private animateSelection ( isSelected ) { this . resetAnimationStyles ( ) ; this . animFrame . request ( AnimationKeys . SELECTION , ( ) => { this . animFrame . request ( AnimationKeys . SELECTION , ( ) => { this . updateSelectionStyles ( isSelected ) ; } ) ; } ) ; } private resetAnimationStyles ( ) { this . adapter . removeClass ( MDCChipCssClasses . SELECTING ) ; this . adapter . removeClass ( MDCChipCssClasses . DESELECTING ) ; this . adapter . removeClass ( MDCChipCssClasses . SELECTING_WITH_PRIMARY_ICON ) ; this . adapter . removeClass ( MDCChipCssClasses . DESELECTING_WITH_PRIMARY_ICON ) ; } private updateSelectionStyles ( isSelected ) { const hasIcon = this . adapter . hasClass ( MDCChipCssClasses . WITH_PRIMARY_ICON ) ; if ( hasIcon && isSelected ) { this . adapter . addClass ( MDCChipCssClasses . SELECTING_WITH_PRIMARY_ICON ) ; this . animFrame . request ( AnimationKeys . SELECTION , ( ) => { this . adapter . addClass ( MDCChipCssClasses . SELECTED ) ; } ) ; return ; } if ( hasIcon && ! isSelected ) { this . adapter . addClass ( MDCChipCssClasses . DESELECTING_WITH_PRIMARY_ICON ) ; this . animFrame . request ( AnimationKeys . SELECTION , ( ) => { this . adapter . removeClass ( MDCChipCssClasses . SELECTED ) ; } ) ; return ; } if ( isSelected ) { this . adapter . addClass ( MDCChipCssClasses . SELECTING ) ; this . animFrame . request ( AnimationKeys . SELECTION , ( ) => { this . adapter . addClass ( MDCChipCssClasses . SELECTED ) ; } ) ; return ; } if ( ! isSelected ) { this . adapter . addClass ( MDCChipCssClasses . DESELECTING ) ; this . animFrame . request ( AnimationKeys . SELECTION , ( ) => { this . adapter . removeClass ( MDCChipCssClasses . SELECTED ) ; } ) ; return ; } } } export default MDCChipFoundation ;