license import { FocusableOption , FocusKeyManager } from '@angular/cdk/a11y' ; import { coerceBooleanProperty } from '@angular/cdk/coercion' ; import { SelectionModel } from '@angular/cdk/collections' ; import { SPACE , ENTER , HOME , END , UP_ARROW , DOWN_ARROW , A , hasModifierKey , } from '@angular/cdk/keycodes' ; import { AfterContentInit , Attribute , ChangeDetectionStrategy , ChangeDetectorRef , Component , ContentChild , ContentChildren , ElementRef , EventEmitter , forwardRef , Inject , Input , OnDestroy , OnInit , Output , QueryList , ViewChild , ViewEncapsulation , } from '@angular/core' ; import { CanDisableRipple , CanDisableRippleCtor , MatLine , setLines , mixinDisableRipple , } from '@angular/material/core' ; import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ; import { Subscription } from 'rxjs' ; import { MatListAvatarCssMatStyler , MatListIconCssMatStyler } from './list' ; docs-private export class MatSelectionListBase { } export const _MatSelectionListMixinBase = mixinDisableRipple ( MatSelectionListBase ) ; docs-private export class MatListOptionBase { } export const _MatListOptionMixinBase = mixinDisableRipple ( MatListOptionBase ) ; docs-private export const MAT_SELECTION_LIST_VALUE_ACCESSOR = { provide : NG_VALUE_ACCESSOR , useExisting : forwardRef ( ( ) => MatSelectionList ) , multi : true } ; /** Change event that is being fired whenever the selected state of an option changes. */ export class MatSelectionListChange { constructor ( /** Reference to the selection list that emitted the event. */ public source , /** Reference to the option that has been changed. */ public option ) { } } /**
 * Component for list-options of selection-list. Each list-option can automatically
 * generate a checkbox and can put current item into the selectionModel of selection-list
 * if the current item is selected.
 */ @ Component ( { moduleId : module . id , selector : 'mat-list-option' , exportAs : 'matListOption' , inputs : [ 'disableRipple' ] , host : { 'role' : 'option' , 'class' : 'mat-list-item mat-list-option' , '(focus)' : '_handleFocus()' , '(blur)' : '_handleBlur()' , '(click)' : '_handleClick()' , 'tabindex' : '-1' , '[class.mat-list-item-disabled]' : 'disabled' , '[class.mat-list-item-with-avatar]' : '_avatar || _icon' , '[attr.aria-selected]' : 'selected.toString()' , '[attr.aria-disabled]' : 'disabled.toString()' , } , templateUrl : 'list-option.html' , encapsulation : ViewEncapsulation . None , changeDetection : ChangeDetectionStrategy . OnPush , } ) export class MatListOption extends _MatListOptionMixinBase implements AfterContentInit , OnDestroy , OnInit , FocusableOption , CanDisableRipple { private _selected = false ; private _disabled = false ; private _hasFocus = false ; @ ContentChild ( MatListAvatarCssMatStyler ) _avatar : MatListAvatarCssMatStyler ; @ ContentChild ( MatListIconCssMatStyler ) _icon : MatListIconCssMatStyler ; @ ContentChildren ( MatLine ) _lines : QueryList < MatLine > ; /** DOM element containing the item's text. */ @ ViewChild ( 'text' ) _text : ElementRef ; /** Whether the label should appear before or after the checkbox. Defaults to 'after' */ @ Input ( ) checkboxPosition : 'before' | 'after' = 'after' ; /** Value of the option */ @ Input ( ) value : any ; /** Whether the option is disabled. */ @ Input ( ) get disabled ( ) { return this . _disabled || ( this . selectionList && this . selectionList . disabled ) ; } set disabled ( value ) { const newValue = coerceBooleanProperty ( value ) ; if ( newValue !== this . _disabled ) { this . _disabled = newValue ; this . _changeDetector . markForCheck ( ) ; } } /** Whether the option is selected. */ @ Input ( ) get selected ( ) : boolean { return this . selectionList . selectedOptions . isSelected ( this ) ; } set selected ( value ) { const isSelected = coerceBooleanProperty ( value ) ; if ( isSelected !== this . _selected ) { this . _setSelected ( isSelected ) ; this . selectionList . _reportValueChange ( ) ; } } constructor ( private _element , private _changeDetector , docs-private @ Inject ( forwardRef ( ( ) => MatSelectionList ) ) public selectionList ) { super ( ) ; } ngOnInit ( ) { const wasSelected = this . _selected ; Promise . resolve ( ) . then ( ( ) => { if ( this . _selected || wasSelected ) { this . selected = true ; this . _changeDetector . markForCheck ( ) ; } } ) ; } ngAfterContentInit ( ) { setLines ( this . _lines , this . _element ) ; } ngOnDestroy ( ) { if ( this . selected ) { Promise . resolve ( ) . then ( ( ) => this . selected = false ) ; } const hadFocus = this . _hasFocus ; const newActiveItem = this . selectionList . _removeOptionFromList ( this ) ; if ( hadFocus && newActiveItem ) { newActiveItem . focus ( ) ; } } /** Toggles the selection state of the option. */ toggle ( ) { this . selected = ! this . selected ; } /** Allows for programmatic focusing of the option. */ focus ( ) { this . _element . nativeElement . focus ( ) ; } docs-private getLabel ( ) { return this . _text ? ( this . _text . nativeElement . textContent || '' ) : '' ; } /** Whether this list item should show a ripple effect when clicked. */ _isRippleDisabled ( ) { return this . disabled || this . disableRipple || this . selectionList . disableRipple ; } _handleClick ( ) { if ( ! this . disabled ) { this . toggle ( ) ; this . selectionList . _emitChangeEvent ( this ) ; } } _handleFocus ( ) { this . selectionList . _setFocusedOption ( this ) ; this . _hasFocus = true ; } _handleBlur ( ) { this . selectionList . _onTouched ( ) ; this . _hasFocus = false ; } /** Retrieves the DOM element of the component host. */ _getHostElement ( ) { return this . _element . nativeElement ; } /** Sets the selected state of the option. Returns whether the value has changed. */ _setSelected ( selected ) { if ( selected === this . _selected ) { return false ; } this . _selected = selected ; if ( selected ) { this . selectionList . selectedOptions . select ( this ) ; } else { this . selectionList . selectedOptions . deselect ( this ) ; } this . _changeDetector . markForCheck ( ) ; return true ; } /**
     * Notifies Angular that the option needs to be checked in the next change detection run. Mainly
     * used to trigger an update of the list option if the disabled state of the selection list
     * changed.
     */ _markForCheck ( ) { this . _changeDetector . markForCheck ( ) ; } } /**
 * Material Design list component where each item is a selectable option. Behaves as a listbox.
 */ @ Component ( { moduleId : module . id , selector : 'mat-selection-list' , exportAs : 'matSelectionList' , inputs : [ 'disabled' , 'disableRipple' , 'tabIndex' ] , host : { 'role' : 'listbox' , '[tabIndex]' : 'tabIndex' , 'class' : 'mat-selection-list mat-list-base' , '(focus)' : 'focus()' , '(blur)' : '_onTouched()' , '(keydown)' : '_keydown($event)' , 'aria-multiselectable' : 'true' , '[attr.aria-disabled]' : 'disabled.toString()' , } , template : '<ng-content></ng-content>' , styleUrls : [ 'list.css' ] , encapsulation : ViewEncapsulation . None , providers : [ MAT_SELECTION_LIST_VALUE_ACCESSOR ] , changeDetection : ChangeDetectionStrategy . OnPush } ) export class MatSelectionList extends _MatSelectionListMixinBase implements FocusableOption , CanDisableRipple , AfterContentInit , ControlValueAccessor , OnDestroy { /** The FocusKeyManager which handles focus. */ _keyManager : FocusKeyManager < MatListOption > ; /** The option components contained within this selection-list. */ @ ContentChildren ( MatListOption ) options : QueryList < MatListOption > ; /** Emits a change event whenever the selected state of an option changes. */ @ Output ( ) readonly selectionChange : EventEmitter < MatSelectionListChange > = new EventEmitter < MatSelectionListChange > ( ) ; /** Tabindex of the selection list. */ @ Input ( ) tabIndex : number = 0 ; /**
     * Function used for comparing an option against the selected value when determining which
     * options should appear as selected. The first argument is the value of an options. The second
     * one is a value from the selected value. A boolean must be returned.
     */ @ Input ( ) compareWith : ( o1 : any , o2 : any ) => boolean ; /** Whether the selection list is disabled. */ @ Input ( ) get disabled ( ) : boolean { return this . _disabled ; } set disabled ( value ) { this . _disabled = coerceBooleanProperty ( value ) ; if ( this . options ) { this . options . forEach ( option => option . _markForCheck ( ) ) ; } } private _disabled : boolean = false ; /** The currently selected options. */ selectedOptions : SelectionModel < MatListOption > = new SelectionModel < MatListOption > ( true ) ; /** View to model callback that should be called whenever the selected options change. */ private _onChange : ( value : any ) => void = ( _ ) => { } ; /** Used for storing the values that were assigned before the options were initialized. */ private _tempValues : string [ ] | null ; /** Subscription to sync value changes in the SelectionModel back to the SelectionList. */ private _modelChanges = Subscription . EMPTY ; /** View to model callback that should be called if the list or its options lost focus. */ _onTouched : ( ) => void = ( ) => { } ; constructor ( private _element , @ Attribute ( 'tabindex' ) tabIndex ) { super ( ) ; this . tabIndex = parseInt ( tabIndex ) || 0 ; } ngAfterContentInit ( ) { this . _keyManager = new FocusKeyManager < MatListOption > ( this . options ) . withWrap ( ) . withTypeAhead ( ) . skipPredicate ( ( ) => false ) . withAllowedModifierKeys ( [ 'shiftKey' ] ) ; if ( this . _tempValues ) { this . _setOptionsFromValues ( this . _tempValues ) ; this . _tempValues = null ; } this . _modelChanges = this . selectedOptions . onChange . subscribe ( event => { if ( event . added ) { for ( let item of event . added ) { item . selected = true ; } } if ( event . removed ) { for ( let item of event . removed ) { item . selected = false ; } } } ) ; } ngOnDestroy ( ) { this . _modelChanges . unsubscribe ( ) ; } /** Focuses the last active list option. */ focus ( ) { this . _element . nativeElement . focus ( ) ; } /** Selects all of the options. */ selectAll ( ) { this . _setAllOptionsSelected ( true ) ; } /** Deselects all of the options. */ deselectAll ( ) { this . _setAllOptionsSelected ( false ) ; } /** Sets the focused option of the selection-list. */ _setFocusedOption ( option ) { this . _keyManager . updateActiveItemIndex ( this . _getOptionIndex ( option ) ) ; } returns _removeOptionFromList ( option ) { const optionIndex = this . _getOptionIndex ( option ) ; if ( optionIndex > - 1 && this . _keyManager . activeItemIndex === optionIndex ) { if ( optionIndex > 0 ) { this . _keyManager . updateActiveItemIndex ( optionIndex - 1 ) ; } else if ( optionIndex === 0 && this . options . length > 1 ) { this . _keyManager . updateActiveItemIndex ( Math . min ( optionIndex + 1 , this . options . length - 1 ) ) ; } } return this . _keyManager . activeItem ; } /** Passes relevant key presses to our key manager. */ _keydown ( event ) { const keyCode = event . keyCode ; const manager = this . _keyManager ; const previousFocusIndex = manager . activeItemIndex ; const hasModifier = hasModifierKey ( event ) ; switch ( keyCode ) { case SPACE : case ENTER : if ( ! hasModifier ) { this . _toggleFocusedOption ( ) ; event . preventDefault ( ) ; } break ; case HOME : case END : if ( ! hasModifier ) { keyCode === HOME ? manager . setFirstItemActive ( ) : manager . setLastItemActive ( ) ; event . preventDefault ( ) ; } break ; case A : if ( hasModifierKey ( event , 'ctrlKey' ) ) { this . options . find ( option => ! option . selected ) ? this . selectAll ( ) : this . deselectAll ( ) ; event . preventDefault ( ) ; } break ; default : manager . onKeydown ( event ) ; } if ( ( keyCode === UP_ARROW || keyCode === DOWN_ARROW ) && event . shiftKey && manager . activeItemIndex !== previousFocusIndex ) { this . _toggleFocusedOption ( ) ; } } /** Reports a value change to the ControlValueAccessor */ _reportValueChange ( ) { if ( this . options ) { this . _onChange ( this . _getSelectedOptionValues ( ) ) ; } } /** Emits a change event if the selected state of an option changed. */ _emitChangeEvent ( option ) { this . selectionChange . emit ( new MatSelectionListChange ( this , option ) ) ; } /** Implemented as part of ControlValueAccessor. */ writeValue ( values ) { if ( this . options ) { this . _setOptionsFromValues ( values || [ ] ) ; } else { this . _tempValues = values ; } } /** Implemented as a part of ControlValueAccessor. */ setDisabledState ( isDisabled ) { this . disabled = isDisabled ; } /** Implemented as part of ControlValueAccessor. */ registerOnChange ( fn ) { this . _onChange = fn ; } /** Implemented as part of ControlValueAccessor. */ registerOnTouched ( fn ) { this . _onTouched = fn ; } /** Sets the selected options based on the specified values. */ private _setOptionsFromValues ( values ) { this . options . forEach ( option => option . _setSelected ( false ) ) ; values . forEach ( value => { const correspondingOption = this . options . find ( option => { if ( option . selected ) { return false ; } return this . compareWith ? this . compareWith ( option . value , value ) : option . value === value ; } ) ; if ( correspondingOption ) { correspondingOption . _setSelected ( true ) ; } } ) ; } /** Returns the values of the selected options. */ private _getSelectedOptionValues ( ) { return this . options . filter ( option => option . selected ) . map ( option => option . value ) ; } /** Toggles the state of the currently focused option if enabled. */ private _toggleFocusedOption ( ) { let focusedIndex = this . _keyManager . activeItemIndex ; if ( focusedIndex != null && this . _isValidIndex ( focusedIndex ) ) { let focusedOption = this . options . toArray ( ) [ focusedIndex ] ; if ( focusedOption && ! focusedOption . disabled ) { focusedOption . toggle ( ) ; this . _emitChangeEvent ( focusedOption ) ; } } } /**
     * Sets the selected state on all of the options
     * and emits an event if anything changed.
     */ private _setAllOptionsSelected ( isSelected ) { let hasChanged = false ; this . options . forEach ( option => { if ( option . _setSelected ( isSelected ) ) { hasChanged = true ; } } ) ; if ( hasChanged ) { this . _reportValueChange ( ) ; } } param index returns private _isValidIndex ( index ) { return index >= 0 && index < this . options . length ; } /** Returns the index of the specified list option. */ private _getOptionIndex ( option ) { return this . options . toArray ( ) . indexOf ( option ) ; } }