import { Component , Input , EventEmitter , Output , forwardRef , ViewEncapsulation , ContentChildren , QueryList , OnDestroy , AfterContentInit , ChangeDetectionStrategy , ChangeDetectorRef , OnChanges , HostListener , HostBinding } from '@angular/core' ; import { NG_VALUE_ACCESSOR , ControlValueAccessor } from '@angular/forms' ; import { Subject } from 'rxjs' ; import { takeUntil } from 'rxjs/operators' ; import { RadioButtonComponent } from './radiobutton.component' ; import { KeyboardKeys } from '../../enums/keyboard-keys.enum' ; import { coerceBooleanProperty , coerceNumberProperty } from '@angular/cdk/coercion' ; const RADIOGROUP_VALUE_ACCESSOR = { provide : NG_VALUE_ACCESSOR , useExisting : forwardRef ( ( ) => RadioButtonGroupComponent ) , multi : true } ; let nextId = 0 ; function mod ( v , n ) { return ( ( v % n ) + n ) % n ; } @ Component ( { exportAs : 'ngxRadiobuttonGroup' , selector : 'ngx-radiobutton-group' , providers : [ RADIOGROUP_VALUE_ACCESSOR ] , template : ' <ng-content></ng-content> ' , styleUrls : [ './radiobutton.component.scss' ] , host : { class : 'ngx-radiobutton-group' , '[class.disabled]' : 'disabled' } , encapsulation : ViewEncapsulation . None , changeDetection : ChangeDetectionStrategy . OnPush } ) export class RadioButtonGroupComponent implements ControlValueAccessor , OnDestroy , OnChanges , AfterContentInit { readonly UNIQUE_ID = `ngx-radio-group-${ ++ nextId }` ; @ Input ( ) id : string = this . UNIQUE_ID ; @ Input ( ) get disabled ( ) { return this . _disabled ; } set disabled ( val ) { this . _disabled = coerceBooleanProperty ( val ) ; this . _updateRadioDisabledState ( ) ; } @ Input ( ) get value ( ) : any { return this . _value ; } set value ( value ) { if ( this . _value !== value ) { this . _value = value ; this . update ( ) ; this . onChangeCallback ( this . _value ) ; } } @ Input ( ) get name ( ) { return this . _name ; } set name ( name ) { if ( this . _name !== name ) { this . _name = name ; this . _updateRadioButtonNames ( ) ; } } @ Input ( ) get focusIndex ( ) { return this . _focusIndex ; } set focusIndex ( val ) { this . _focusIndex = coerceNumberProperty ( val ) ; this . focusOn ( this . _focusIndex ) ; } @ HostBinding ( 'attr.tabindex' ) @ Input ( ) get tabIndex ( ) { return this . disabled ? - 1 : this . _tabIndex ; } set tabIndex ( val ) { this . _tabIndex = coerceNumberProperty ( val ) ; } @ Output ( ) change = new EventEmitter < boolean > ( ) ; @ Output ( ) blur = new EventEmitter < Event > ( ) ; @ Output ( ) focus = new EventEmitter < FocusEvent > ( ) ; @ ContentChildren ( forwardRef ( ( ) => RadioButtonComponent ) , { descendants : true } ) readonly _radios : QueryList < RadioButtonComponent > ; get selected ( ) : RadioButtonComponent { return this . _selected ; } private _name : string = this . UNIQUE_ID ; private _value = false ; private _disabled = false ; private _selected : RadioButtonComponent ; private _focusIndex = - 1 ; private _tabIndex = 0 ; private _destroy$ = new Subject < void > ( ) ; constructor ( private readonly _cdr ) { } ngAfterContentInit ( ) { this . subscribeToRadios ( ) ; if ( this . _radios ) { this . _radios . changes . subscribe ( this . subscribeToRadios . bind ( this ) ) ; } this . update ( ) ; } ngOnDestroy ( ) { this . _destroy$ . next ( ) ; this . _destroy$ . complete ( ) ; } ngOnChanges ( ) { this . update ( ) ; } @ HostListener ( 'focus' ) onFocus ( ) { if ( this . selected ) { this . focusIndex = this . _radios . toArray ( ) . indexOf ( this . selected ) ; } else { this . focusFirst ( ) ; } } @ HostListener ( 'keydown' , [ '$event' ] ) onKeyUp ( ev ) { switch ( ev . code ) { case KeyboardKeys . ARROW_LEFT : case KeyboardKeys . ARROW_UP : ev . stopPropagation ( ) ; ev . preventDefault ( ) ; this . focusIn ( - 1 ) ; this . selectIndex ( this . focusIndex ) ; break ; case KeyboardKeys . ARROW_RIGHT : case KeyboardKeys . ARROW_DOWN : ev . stopPropagation ( ) ; ev . preventDefault ( ) ; this . focusIn ( 1 ) ; this . selectIndex ( this . focusIndex ) ; break ; } } subscribeToRadios ( ) { this . _destroy$ . next ( ) ; if ( this . _radios ) { this . _radios . map ( radio => { radio . change . pipe ( takeUntil ( this . _destroy$ ) ) . subscribe ( this . onRadioSelected . bind ( this ) ) ; } ) ; } this . update ( ) ; } onRadioSelected ( value ) { if ( this . value !== value ) { setTimeout ( ( ) => { this . value = value ; } ) ; } } writeValue ( value ) { this . value = value ; this . update ( ) ; } registerOnChange ( fn ) { this . onChangeCallback = fn ; } registerOnTouched ( fn ) { this . onTouchedCallback = fn ; } onChangeCallback ( _ ) { } onTouchedCallback ( ) { } focusFirst ( ) { if ( ! this . disabled && this . _radios ) { const len = this . _radios . length ; for ( let i = 0 ; i < len ; i ++ ) { if ( ! this . _radios . get ( i ) . disabled ) { this . focusIndex = i ; break ; } } } } setDisabledState ( isDisabled ) { this . disabled = coerceBooleanProperty ( isDisabled ) ; } private selectIndex ( index ) { if ( ! this . disabled && this . focusIndex > - 1 ) { this . value = this . _radios . get ( index ) . value ; } } private focusOn ( index ) { if ( ! this . disabled ) { this . _radios . get ( index ) . focusElement ( ) ; } } /**
     * Moves focus to next radio button in the group.
     * +1 is next radio button, -1 is previous radio button.
     */ private focusIn ( dir ) { if ( ! this . disabled && this . _radios ) { const len = this . _radios . length ; for ( let i = 1 ; i < len ; i ++ ) { const ii = mod ( this . focusIndex + dir * i , len ) ; if ( ! this . _radios . get ( ii ) . disabled ) { this . focusIndex = ii ; return ; } } } } private update ( ) { this . _updateSelectedRadioFromValue ( ) ; this . _updateRadioDisabledState ( ) ; this . _cdr . markForCheck ( ) ; } private _updateRadioButtonNames ( ) { if ( this . _radios ) { this . _radios . forEach ( radio => { radio . name = this . name ; } ) ; } } private _updateSelectedRadioFromValue ( ) { if ( this . _radios ) { this . _radios . forEach ( radio => { radio . checked = this . value === radio . value ; radio . isInGroup = true ; if ( radio . checked ) { this . _selected = radio ; } } ) ; } } private _updateRadioDisabledState ( ) { if ( this . _radios ) { this . _radios . forEach ( radio => { radio . groupDisabled = this . disabled ; } ) ; } } }