import { AfterContentInit , ChangeDetectorRef , Component , ElementRef , Input , OnDestroy , Optional , Renderer , ViewChild , ViewEncapsulation } from '@angular/core' ; import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ; import { clamp , isTrueProperty } from '../../util/util' ; import { Config } from '../../config/config' ; import { DomController } from '../../platform/dom-controller' ; import { Form } from '../../util/form' ; import { Haptic } from '../../tap-click/haptic' ; import { BaseInput } from '../../util/base-input' ; import { Item } from '../item/item' ; import { Platform } from '../../platform/platform' ; import { PointerCoordinates , pointerCoord } from '../../util/dom' ; import { UIEventManager } from '../../gestures/ui-event-manager' ; name description usage demo @ Component ( { selector : 'ion-range' , template : '<ng-content select="[range-left]"></ng-content>' + '<div class="range-slider" #slider>' + '<div class="range-tick" *ngFor="let t of _ticks" [style.left]="t.left" [class.range-tick-active]="t.active" role="presentation"></div>' + '<div class="range-bar" role="presentation"></div>' + '<div class="range-bar range-bar-active" [style.left]="_barL" [style.right]="_barR" #bar role="presentation"></div>' + '<div class="range-knob-handle" (ionIncrease)="_keyChg(true, false)" (ionDecrease)="_keyChg(false, false)" [ratio]="_ratioA" [val]="_valA" [pin]="_pin" [pressed]="_pressedA" [min]="_min" [max]="_max" [disabled]="_disabled" [labelId]="_labelId"></div>' + '<div class="range-knob-handle" (ionIncrease)="_keyChg(true, true)" (ionDecrease)="_keyChg(false, true)" [ratio]="_ratioB" [val]="_valB" [pin]="_pin" [pressed]="_pressedB" [min]="_min" [max]="_max" [disabled]="_disabled" [labelId]="_labelId" *ngIf="_dual"></div>' + '</div>' + '<ng-content select="[range-right]"></ng-content>' , host : { '[class.range-disabled]' : '_disabled' , '[class.range-pressed]' : '_pressed' , '[class.range-has-pin]' : '_pin' } , providers : [ { provide : NG_VALUE_ACCESSOR , useExisting : Range , multi : true } ] , encapsulation : ViewEncapsulation . None , } ) export class Range extends BaseInput < any > implements AfterContentInit , ControlValueAccessor , OnDestroy { _dual : boolean ; _pin : boolean ; _pressed : boolean ; _activeB : boolean ; _rect : ClientRect ; _ticks : any [ ] ; _min = 0 ; _max = 100 ; _step = 1 ; _snaps : boolean ; _valA = 0 ; _valB = 0 ; _ratioA = 0 ; _ratioB = 0 ; _pressedA : boolean ; _pressedB : boolean ; _barL : string ; _barR : string ; _events : UIEventManager ; @ ViewChild ( 'slider' ) public _slider : ElementRef ; input @ Input ( ) get min ( ) : number { return this . _min ; } set min ( val ) { val = Math . round ( val ) ; if ( ! isNaN ( val ) ) { this . _min = val ; this . _inputUpdated ( ) ; } } input @ Input ( ) get max ( ) : number { return this . _max ; } set max ( val ) { val = Math . round ( val ) ; if ( ! isNaN ( val ) ) { this . _max = val ; this . _inputUpdated ( ) ; } } input @ Input ( ) get step ( ) : number { return this . _step ; } set step ( val ) { val = Math . round ( val ) ; if ( ! isNaN ( val ) && val > 0 ) { this . _step = val ; } } input @ Input ( ) get snaps ( ) : boolean { return this . _snaps ; } set snaps ( val ) { this . _snaps = isTrueProperty ( val ) ; } input @ Input ( ) get pin ( ) : boolean { return this . _pin ; } set pin ( val ) { this . _pin = isTrueProperty ( val ) ; } input @ Input ( ) get debounce ( ) : number { return this . _debouncer . wait ; } set debounce ( val ) { this . _debouncer . wait = val ; } input @ Input ( ) get dualKnobs ( ) : boolean { return this . _dual ; } set dualKnobs ( val ) { this . _dual = isTrueProperty ( val ) ; } /**
     * Returns the ratio of the knob's is current location, which is a number
     * between `0` and `1`. If two knobs are used, this property represents
     * the lower value.
     */ get ratio ( ) : number { if ( this . _dual ) { return Math . min ( this . _ratioA , this . _ratioB ) ; } return this . _ratioA ; } /**
     * Returns the ratio of the upper value's is current location, which is
     * a number between `0` and `1`. If there is only one knob, then this
     * will return `null`.
     */ get ratioUpper ( ) : number { if ( this . _dual ) { return Math . max ( this . _ratioA , this . _ratioB ) ; } return null ; } constructor ( form , private _haptic , @ Optional ( ) item , config , private _plt , elementRef , renderer , private _dom , private _cd ) { super ( config , elementRef , renderer , 'range' , 0 , form , item , null ) ; this . _events = new UIEventManager ( _plt ) ; } hidden ngAfterContentInit ( ) { this . _initialize ( ) ; this . _events . pointerEvents ( { element : this . _slider . nativeElement , pointerDown : this . _pointerDown . bind ( this ) , pointerMove : this . _pointerMove . bind ( this ) , pointerUp : this . _pointerUp . bind ( this ) , zone : true } ) ; this . _createTicks ( ) ; } internal _pointerDown ( ev ) { if ( this . _disabled ) { return false ; } this . _fireFocus ( ) ; ev . preventDefault ( ) ; ev . stopPropagation ( ) ; const current = pointerCoord ( ev ) ; const rect = this . _rect = this . _plt . getElementBoundingClientRect ( this . _slider . nativeElement ) ; const ratio = clamp ( 0 , ( current . x - rect . left ) / ( rect . width ) , 1 ) ; this . _activeB = this . _dual && ( Math . abs ( ratio - this . _ratioA ) > Math . abs ( ratio - this . _ratioB ) ) ; this . _update ( current , rect , true ) ; this . _haptic . gestureSelectionStart ( ) ; return true ; } internal _pointerMove ( ev ) { if ( this . _disabled ) { return ; } ev . preventDefault ( ) ; ev . stopPropagation ( ) ; const hasChanged = this . _update ( pointerCoord ( ev ) , this . _rect , true ) ; if ( hasChanged && this . _snaps ) { this . _haptic . gestureSelectionChanged ( ) ; } } internal _pointerUp ( ev ) { if ( this . _disabled ) { return ; } ev . preventDefault ( ) ; ev . stopPropagation ( ) ; this . _update ( pointerCoord ( ev ) , this . _rect , false ) ; this . _haptic . gestureSelectionEnd ( ) ; this . _fireBlur ( ) ; } internal _update ( current , rect , isPressed ) { let ratio = clamp ( 0 , ( current . x - rect . left ) / ( rect . width ) , 1 ) ; let val = this . _ratioToValue ( ratio ) ; if ( this . _snaps ) { ratio = this . _valueToRatio ( val ) ; } this . _pressed = isPressed ; let valChanged = false ; if ( this . _activeB ) { this . _pressedB = isPressed ; this . _pressedA = false ; this . _ratioB = ratio ; valChanged = val === this . _valB ; this . _valB = val ; } else { this . _pressedA = isPressed ; this . _pressedB = false ; this . _ratioA = ratio ; valChanged = val === this . _valA ; this . _valA = val ; } this . _updateBar ( ) ; if ( valChanged ) { return false ; } let value ; if ( this . _dual ) { value = { lower : Math . min ( this . _valA , this . _valB ) , upper : Math . max ( this . _valA , this . _valB ) } ; console . debug ( `range, updateKnob: ${ ratio }, lower: ${ this . value . lower }, upper: ${ this . value . upper }` ) ; } else { value = this . _valA ; console . debug ( `range, updateKnob: ${ ratio }, value: ${ this . value }` ) ; } this . value = value ; return true ; } internal _updateBar ( ) { const ratioA = this . _ratioA ; const ratioB = this . _ratioB ; if ( this . _dual ) { this . _barL = `${ ( Math . min ( ratioA , ratioB ) * 100 ) }%` ; this . _barR = `${ 100 - ( Math . max ( ratioA , ratioB ) * 100 ) }%` ; } else { this . _barL = '' ; this . _barR = `${ 100 - ( ratioA * 100 ) }%` ; } this . _updateTicks ( ) ; } internal _createTicks ( ) { if ( this . _snaps ) { this . _dom . write ( ( ) => { this . _ticks = [ ] ; for ( var value = this . _min ; value <= this . _max ; value += this . _step ) { var ratio = this . _valueToRatio ( value ) ; this . _ticks . push ( { ratio : ratio , left : `${ ratio * 100 }%` , } ) ; } this . _updateTicks ( ) ; } ) ; } } internal _updateTicks ( ) { const ticks = this . _ticks ; const ratio = this . ratio ; if ( this . _snaps && ticks ) { if ( this . _dual ) { var upperRatio = this . ratioUpper ; ticks . forEach ( t => { t . active = ( t . ratio >= ratio && t . ratio <= upperRatio ) ; } ) ; } else { ticks . forEach ( t => { t . active = ( t . ratio <= ratio ) ; } ) ; } } } hidden _keyChg ( isIncrease , isKnobB ) { const step = this . _step ; if ( isKnobB ) { if ( isIncrease ) { this . _valB += step ; } else { this . _valB -= step ; } this . _valB = clamp ( this . _min , this . _valB , this . _max ) ; this . _ratioB = this . _valueToRatio ( this . _valB ) ; } else { if ( isIncrease ) { this . _valA += step ; } else { this . _valA -= step ; } this . _valA = clamp ( this . _min , this . _valA , this . _max ) ; this . _ratioA = this . _valueToRatio ( this . _valA ) ; } this . _updateBar ( ) ; } internal _ratioToValue ( ratio ) { ratio = Math . round ( ( ( this . _max - this . _min ) * ratio ) ) ; ratio = Math . round ( ratio / this . _step ) * this . _step + this . _min ; return clamp ( this . _min , ratio , this . _max ) ; } internal _valueToRatio ( value ) { value = Math . round ( ( value - this . _min ) / this . _step ) * this . _step ; value = value / ( this . _max - this . _min ) ; return clamp ( 0 , value , 1 ) ; } _inputNormalize ( val ) { if ( this . _dual ) { return val ; } else { val = parseFloat ( val ) ; return isNaN ( val ) ? undefined : val ; } } hidden _inputUpdated ( ) { const val = this . value ; if ( this . _dual ) { this . _valA = val . lower ; this . _valB = val . upper ; this . _ratioA = this . _valueToRatio ( val . lower ) ; this . _ratioB = this . _valueToRatio ( val . upper ) ; } else { this . _valA = val ; this . _ratioA = this . _valueToRatio ( val ) ; } this . _updateBar ( ) ; this . _cd . detectChanges ( ) ; } hidden ngOnDestroy ( ) { super . ngOnDestroy ( ) ; this . _events . destroy ( ) ; } }