import { AfterContentInit , ChangeDetectionStrategy , ChangeDetectorRef , Component , ContentChildren , ElementRef , EventEmitter , forwardRef , Input , OnDestroy , Output , QueryList , ViewEncapsulation } from '@angular/core' ; import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ; import { coerceBooleanProperty } from '@angular/cdk/coercion' ; import { merge , Observable , Subject , Subscription } from 'rxjs' ; import { startWith , takeUntil } from 'rxjs/operators' ; import { MDCComponent } from '@angular-mdc/web/base' ; import { MdcChipInteractionEvent , MdcChipNavigationEvent , MdcChipSelectionEvent , MdcChipRemovalEvent } from './chip-config' ; import { MdcChip , MDC_CHIPSET_PARENT_COMPONENT } from './chip' ; import { MDCChipSetFoundation , MDCChipSetAdapter } from '@material/chips' ; /**
 * Provider Expression that allows mdc-chip-set to register as a ControlValueAccessor. This
 * allows it to support [(ngModel)] and ngControl.
 */ export const MDC_CHIP_SET_CONTROL_VALUE_ACCESSOR = { provide : NG_VALUE_ACCESSOR , useExisting : forwardRef ( ( ) => MdcChipSet ) , multi : true } ; export class MdcChipSetChange { constructor ( public source , public value ) { } } @ Component ( { selector : 'mdc-chip-set' , exportAs : 'mdcChipSet' , host : { 'role' : 'grid' , 'class' : 'mdc-chip-set' , '[class.mdc-chip-set--choice]' : 'choice' , '[class.mdc-chip-set--filter]' : 'filter' , '[class.mdc-chip-set--input]' : 'input' } , template : '<ng-content></ng-content>' , encapsulation : ViewEncapsulation . None , changeDetection : ChangeDetectionStrategy . OnPush , providers : [ MDC_CHIP_SET_CONTROL_VALUE_ACCESSOR , { provide : MDC_CHIPSET_PARENT_COMPONENT , useExisting : MdcChipSet } ] } ) export class MdcChipSet extends MDCComponent < MDCChipSetFoundation > implements AfterContentInit , OnDestroy , ControlValueAccessor { /** Emits whenever the component is destroyed. */ private _destroyed = new Subject < void > ( ) ; /**
    * Indicates that the chips in the set are choice chips, which allow a single selection from a set of options.
    */ @ Input ( ) get choice ( ) : boolean { return this . _choice ; } set choice ( value ) { this . _choice = coerceBooleanProperty ( value ) ; } private _choice = false ; /**
    * Indicates that the chips in the set are filter chips, which allow multiple selection from a set of options.
    */ @ Input ( ) get filter ( ) : boolean { return this . _filter ; } set filter ( value ) { this . _filter = coerceBooleanProperty ( value ) ; } private _filter = false ; /**
    * Indicates that the chips in the set are input chips, which enable user input by converting text into chips.
    */ @ Input ( ) get input ( ) : boolean { return this . _input ; } set input ( value ) { this . _input = coerceBooleanProperty ( value ) ; } private _input = false ; @ Input ( ) get value ( ) : string | string [ ] | undefined { return this . _value ; } set value ( value ) { this . _value = value ; this . writeValue ( value ) ; } private _value ? : string | string [ ] ; /**
     * A function to compare the option values with the selected values. The first argument
     * is a value from an option. The second is a value from the selection. A boolean
     * should be returned.
     */ private _compareWith = ( o1 , o2 ) => o1 === o2 ; @ Output ( ) readonly change : EventEmitter < MdcChipSetChange > = new EventEmitter < MdcChipSetChange > ( ) ; @ Output ( ) readonly interaction : EventEmitter < MdcChipInteractionEvent > = new EventEmitter < MdcChipInteractionEvent > ( ) ; @ ContentChildren ( MdcChip , { descendants : true } ) chips ! : QueryList < MdcChip > ; /** Function when touched */ _onTouched = ( ) => { } ; /** Function when changed */ _onChange : ( value : any ) => void = ( ) => { } ; /** Subscription to selection events in chips. */ private _chipSelectionSubscription : Subscription | null = null ; /** Subscription to removal changes. */ private _chipRemovalSubscription : Subscription | null = null ; /** Subscription to interaction events in chips. */ private _chipInteractionSubscription : Subscription | null = null ; /** Subscription to navigation events. */ private _navigationSubscription : Subscription | null = null ; /** Combined stream of all of the chip selection events. */ get chipSelections ( ) : Observable < MdcChipSelectionEvent > { return merge ( ... this . chips . map ( chip => chip . selectionChange ) ) ; } /** Combined stream of all of the chip interaction events. */ get chipInteractions ( ) : Observable < MdcChipInteractionEvent > { return merge ( ... this . chips . map ( chip => chip . interactionEvent ) ) ; } /** Combined stream of all of the chip removal events. */ get chipRemovalChanges ( ) : Observable < MdcChipRemovalEvent > { return merge ( ... this . chips . map ( chip => chip . removalEvent ) ) ; } /** Combined stream of all of the chip navigation events. */ get chipNavigations ( ) : Observable < MdcChipNavigationEvent > { return merge ( ... this . chips . map ( chip => chip . navigationEvent ) ) ; } getDefaultFoundation ( ) { const adapter = { hasClass : ( className ) => this . _getHostElement ( ) . classList . contains ( className ) , focusChipPrimaryActionAtIndex : ( index ) => this . chips . toArray ( ) [ index ] . focusPrimaryAction ( ) , focusChipTrailingActionAtIndex : ( index ) => this . chips . toArray ( ) [ index ] . focusTrailingAction ( ) , getChipListCount : ( ) => this . chips . length , getIndexOfChipById : ( chipId ) => this . _findChipIndex ( chipId ) , removeChipAtIndex : ( index ) => { if ( index >= 0 && index < this . chips . length ) { this . chips . toArray ( ) [ index ] . destroy ( ) ; this . chips . toArray ( ) [ index ] . remove ( ) ; this . chips . toArray ( ) . splice ( index , 1 ) ; } } , removeFocusFromChipAtIndex : ( index ) => this . chips . toArray ( ) [ index ] . removeFocus ( ) , selectChipAtIndex : ( index , selected , shouldNotifyClients ) => { if ( index >= 0 && index < this . chips . length ) { this . chips . toArray ( ) [ index ] . setSelectedFromChipSet ( selected , shouldNotifyClients ) ; } } , announceMessage : ( ) => { } , isRTL : ( ) => typeof window !== 'undefined' ? window . getComputedStyle ( this . _getHostElement ( ) ) . getPropertyValue ( 'direction' ) === 'rtl' : false } ; return new MDCChipSetFoundation ( adapter ) ; } constructor ( private _changeDetectorRef , public elementRef ) { super ( elementRef ) ; } ngAfterContentInit ( ) { this . chips . changes . pipe ( startWith ( null ) , takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => { this . _resetChipSet ( ) ; if ( this . chips . length >= 0 ) { this . _initializeSelection ( ) ; } } ) ; } ngOnDestroy ( ) { this . _destroyed . next ( ) ; this . _destroyed . complete ( ) ; this . _dropSubscriptions ( ) ; this . _chipRemovalSubscription ?. unsubscribe ( ) ; this . _foundation ?. destroy ( ) ; } writeValue ( value ) { this . selectByValue ( value , true ) ; } registerOnChange ( fn ) { this . _onChange = fn ; } registerOnTouched ( fn ) { this . _onTouched = fn ; } getSelectedChipIds ( ) { return this . _foundation . getSelectedChipIds ( ) ; } select ( chipId ) { this . _foundation . select ( chipId ) ; } selectByValue ( value , shouldIgnore = true ) { if ( ! this . chips ) { return ; } if ( Array . isArray ( value ) ) { value . forEach ( currentValue => this . _selectValue ( currentValue , shouldIgnore ) ) ; } else { this . _selectValue ( value , shouldIgnore ) ; } this . _value = value ; } returns private _selectValue ( value , shouldIgnore = true ) { const correspondingChip = this . chips . find ( chip => chip . value != null && this . _compareWith ( chip . value , value ) ) ; if ( correspondingChip ) { if ( this . choice ) { this . select ( correspondingChip . id ) ; } else { correspondingChip . setSelectedFromChipSet ( true , shouldIgnore ) ; } } return correspondingChip ; } private _initializeSelection ( ) { Promise . resolve ( ) . then ( ( ) => { if ( this . _value ) { this . selectByValue ( this . _value , false ) ; } } ) ; } private _propagateChanges ( evt ) { this . _value = evt . value ; this . change . emit ( new MdcChipSetChange ( this , evt ) ) ; this . _onChange ( this . _value ) ; this . _changeDetectorRef . markForCheck ( ) ; } private _findChipIndex ( chipId ) { return this . chips . toArray ( ) . findIndex ( _ => _ . id === chipId ) ; } private _resetChipSet ( ) { this . _dropSubscriptions ( ) ; this . _listenToChipEvents ( ) ; } private _dropSubscriptions ( ) { this . _chipSelectionSubscription ?. unsubscribe ( ) ; this . _chipInteractionSubscription ?. unsubscribe ( ) ; this . _chipRemovalSubscription ?. unsubscribe ( ) ; this . _navigationSubscription ?. unsubscribe ( ) ; } /** Listens to selected events on each chip. */ private _listenToChipEvents ( ) { this . _chipSelectionSubscription = this . chipSelections . subscribe ( ( event ) => { this . _foundation . handleChipSelection ( event ) ; this . _propagateChanges ( event ) ; } ) ; this . _chipInteractionSubscription = this . chipInteractions . subscribe ( ( event ) => { this . _foundation . handleChipInteraction ( event ) ; this . interaction . emit ( event ) ; } ) ; this . _chipRemovalSubscription = this . chipRemovalChanges . subscribe ( ( event ) => this . _foundation . handleChipRemoval ( event ) ) ; this . _navigationSubscription = this . chipNavigations . subscribe ( ( event ) => this . _foundation . handleChipNavigation ( event ) ) ; } /** Retrieves the DOM element of the component host. */ private _getHostElement ( ) { return this . elementRef . nativeElement ; } }