• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 6309146873

26 Sep 2023 06:55AM CUT coverage: 92.235% (-0.02%) from 92.256%
6309146873

push

github

web-flow
fix(slider): properly adjusting bounds #13403 (#13472)

15302 of 17983 branches covered (0.0%)

1 of 1 new or added line in 1 file covered. (100.0%)

26833 of 29092 relevant lines covered (92.23%)

29478.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.06
/projects/igniteui-angular/src/lib/slider/slider.component.ts
1
import { NgIf } from '@angular/common';
2
import {
3
    AfterContentInit, AfterViewInit, ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter,
4
    HostBinding, HostListener, Input, NgZone, OnChanges, OnDestroy, OnInit, Output, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChild, ViewChildren
5
} from '@angular/core';
6
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
7
import { merge, noop, Observable, Subject, timer } from 'rxjs';
8
import { takeUntil, throttleTime } from 'rxjs/operators';
9
import { EditorProvider } from '../core/edit-provider';
10
import { resizeObservable } from '../core/utils';
11
import { IgxDirectionality } from '../services/direction/directionality';
12
import { IgxThumbLabelComponent } from './label/thumb-label.component';
13
import {
14
    IgxSliderType, IgxThumbFromTemplateDirective,
15
    IgxThumbToTemplateDirective, IgxTickLabelTemplateDirective, IRangeSliderValue, ISliderValueChangeEventArgs, SliderHandle, TickLabelsOrientation, TicksOrientation
16
} from './slider.common';
2✔
17
import { IgxSliderThumbComponent } from './thumb/thumb-slider.component';
18
import { IgxTickLabelsPipe } from './ticks/tick.pipe';
19
import { IgxTicksComponent } from './ticks/ticks.component';
20

21
let NEXT_ID = 0;
22

23
/**
24
 * **Ignite UI for Angular Slider** -
25
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/slider/slider)
26
 *
27
 * The Ignite UI Slider allows selection in a given range by moving the thumb along the track. The track
28
 * can be defined as continuous or stepped, and you can choose between single and range slider types.
29
 *
30
 * Example:
31
 * ```html
32
 * <igx-slider id="slider"
2✔
33
 *            [minValue]="0" [maxValue]="100"
34
 *            [continuous]=true [(ngModel)]="volume">
35
 * </igx-slider>
36
 * ```
37
 */
489✔
38
@Component({
39
    providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: IgxSliderComponent, multi: true }],
40
    selector: 'igx-slider',
41
    templateUrl: 'slider.component.html',
42
    standalone: true,
43
    imports: [NgIf, IgxTicksComponent, IgxThumbLabelComponent, IgxSliderThumbComponent, IgxTickLabelsPipe]
662✔
44
})
45
export class IgxSliderComponent implements
46
    ControlValueAccessor,
152✔
47
    EditorProvider,
48
    OnInit,
49
    AfterViewInit,
388✔
50
    AfterContentInit,
51
    OnChanges,
52
    OnDestroy {
488✔
53
    /**
54
     * @hidden
55
     */
488✔
56
    public get thumbFrom(): IgxSliderThumbComponent {
57
        return this.thumbs.find(thumb => thumb.type === SliderHandle.FROM);
58
    }
488✔
59

60
    /**
61
     * @hidden
488✔
62
     */
63
    public get thumbTo(): IgxSliderThumbComponent {
64
        return this.thumbs.find(thumb => thumb.type === SliderHandle.TO);
4,404✔
65
    }
66

67
    private get labelFrom(): IgxThumbLabelComponent {
68
        return this.labelRefs.find(label => label.type === SliderHandle.FROM);
69
    }
70

71
    private get labelTo(): IgxThumbLabelComponent {
72
        return this.labelRefs.find(label => label.type === SliderHandle.TO);
73
    }
74

75
    /**
76
     * @hidden
77
     */
40✔
78
    @ViewChild('track', { static: true })
40✔
79
    public trackRef: ElementRef;
10✔
80

81
    /**
40✔
82
     * @hidden
12✔
83
     */
84
    @ContentChild(IgxThumbFromTemplateDirective, { read: TemplateRef })
85
    public thumbFromTemplateRef: TemplateRef<any>;
86

58,482✔
87
    /**
88
     * @hidden
89
     */
37✔
90
    @ContentChild(IgxThumbToTemplateDirective, { read: TemplateRef })
37✔
91
    public thumbToTemplateRef: TemplateRef<any>;
37✔
92

37✔
93
    /**
37✔
94
     * @hidden
14✔
95
     */
14✔
96
    @ContentChild(IgxTickLabelTemplateDirective, { read: TemplateRef, static: false })
97
    public tickLabelTemplateRef: TemplateRef<any>;
98

99
    /**
100
     * @hidden
101
     */
102
    @HostBinding(`attr.role`)
103
    public role = 'slider';
104

105
    /**
106
     * @hidden
107
     */
108
    @HostBinding('class.igx-slider')
109
    public slierClass = true;
110

1,582✔
111
    /**
112
     * An @Input property that sets the value of the `id` attribute.
113
     * If not provided it will be automatically generated.
114
     * ```html
115
     * <igx-slider [id]="'igx-slider-32'" [(ngModel)]="task.percentCompleted" [step]="5" [lowerBound]="20">
116
     * ```
5✔
117
     */
5!
118
    @HostBinding('attr.id')
5✔
119
    @Input()
5✔
120
    public id = `igx-slider-${NEXT_ID++}`;
5✔
121

5✔
122
    /**
5✔
123
     * An @Input property that sets the duration visibility of thumbs labels. The default value is 750 milliseconds.
124
     * ```html
125
     * <igx-slider #slider [thumbLabelVisibilityDuration]="3000" [(ngModel)]="task.percentCompleted" [step]="5">
126
     * ```
127
     */
128
    @Input()
129
    public thumbLabelVisibilityDuration = 750;
130

131
    /**
132
     * @hidden
133
     */
134
    @HostBinding(`attr.aria-valuemin`)
135
    public get valuemin() {
136
        return this.minValue;
1,230✔
137
    }
138

139
    /**
1,939✔
140
     * @hidden
141
     */
142
    @HostBinding(`attr.aria-valuemax`)
143
    public get valuemax() {
144
        return this.maxValue;
145
    }
146

147
    /**
148
     * @hidden
1✔
149
     */
1!
150
    @HostBinding(`attr.aria-readonly`)
1✔
151
    public get readonly() {
152
        return this.disabled;
153
    }
154

1,703✔
155
    /**
156
     * @hidden
157
     */
158
    @HostBinding('class.igx-slider--disabled')
159
    public get disabledClass() {
160
        return this.disabled;
161
    }
162

163
    /**
164
     * An @Input property that gets the type of the `IgxSliderComponent`.
165
     * The slider can be IgxSliderType.SLIDER(default) or IgxSliderType.RANGE.
166
     * ```typescript
1✔
167
     * @ViewChild("slider2")
1!
168
     * public slider: IgxSliderComponent;
1✔
169
     * ngAfterViewInit(){
170
     *     let type = this.slider.type;
171
     * }
172
     */
173
    @Input()
174
    public get type() {
175
        return this._type as IgxSliderType;
176
    }
177

178
    /**
179
     * An @Input property that sets the type of the `IgxSliderComponent`.
180
     * The slider can be IgxSliderType.SLIDER(default) or IgxSliderType.RANGE.
181
     * ```typescript
182
     * sliderType: IgxSliderType = IgxSliderType.RANGE;
10,294✔
183
     * ```
4,287✔
184
     * ```html
185
     * <igx-slider #slider2 [type]="sliderType" [(ngModel)]="rangeValue" [minValue]="0" [maxValue]="100">
6,007✔
186
     * ```
187
     */
188
    public set type(type: IgxSliderType) {
23✔
189
        this._type = type;
2✔
190

191
        if (type === IgxSliderType.SLIDER) {
192
            this.lowerValue = 0;
21✔
193
        }
194

21✔
195
        if (this._hasViewInit) {
1✔
196
            this.updateTrack();
1✔
197
        }
198
    }
199

21✔
200

201
    /**
21✔
202
     * Enables `labelView`, by accepting a collection of primitive values with more than one element.
21✔
203
     * Each element will be equally spread over the slider and it will serve as a thumb label.
10✔
204
     * Once the property is set, it will precendence over {@link maxValue}, {@link minValue}, {@link step}.
10✔
205
     * This means that the manipulation for those properties won't be allowed.
206
     */
207
    @Input()
208
    public get labels() {
209
        return this._labels;
210
    }
211

212
    public set labels(labels: Array<number | string | boolean | null | undefined>) {
213
        this._labels = labels;
214

215
        this._pMax = this.valueToFraction(this.upperBound, 0, 1);
216
        this._pMin = this.valueToFraction(this.lowerBound, 0, 1);
217

218
        this.positionHandlersAndUpdateTrack();
10,777✔
219

220
        if (this._hasViewInit) {
221
            this.stepDistance = this.calculateStepDistance();
222
            this.setTickInterval();
223
        }
23✔
224
    }
1✔
225

226
    /**
227
     * Returns the template context corresponding
22✔
228
     * to {@link IgxThumbFromTemplateDirective} and {@link IgxThumbToTemplateDirective} templates.
229
     *
22✔
230
     * ```typescript
1✔
231
     * return {
1✔
232
     *  $implicit // returns the value of the label,
233
     *  labels // returns the labels collection the user has passed.
234
     * }
22✔
235
     * ```
236
     */
22✔
237
    public get context(): any {
22✔
238
        return {
11✔
239
            $implicit: this.value,
11✔
240
            labels: this.labels
241
        };
242
    }
243

244
    /**
245
     * An @Input property that sets the incremental/decremental step of the value when dragging the thumb.
246
     * The default step is 1, and step should not be less or equal than 0.
247
     * ```html
248
     * <igx-slider #slider [(ngModel)]="task.percentCompleted" [step]="5">
249
     * ```
250
     */
251
    @Input()
252
    public set step(step: number) {
253
        this._step = step;
254

5,654✔
255
        if (this._hasViewInit) {
1,330✔
256
            this.stepDistance = this.calculateStepDistance();
257
            this.normalizeByStep(this._value);
4,324✔
258
            this.setValue(this._value, true);
259
            this.positionHandlersAndUpdateTrack();
260
            this.setTickInterval();
26✔
261
        }
5✔
262
    }
263

21✔
264
    /**
265
     * Returns the incremental/decremental dragging step of the {@link IgxSliderComponent}.
21✔
266
     * ```typescript
21✔
267
     * @ViewChild("slider2")
268
     * public slider: IgxSliderComponent;
269
     * ngAfterViewInit(){
270
     *     let step = this.slider.step;
271
     * }
272
     * ```
273
     */
274
    public get step() {
275
        return this.labelsViewEnabled ? 1 : this._step;
276
    }
277

278
    /**
279
     * Returns if the {@link IgxSliderComponent} is disabled.
280
     * ```typescript
7,407✔
281
     * @ViewChild("slider2")
1,456✔
282
     * public slider: IgxSliderComponent;
283
     * ngAfterViewInit(){
5,951✔
284
     *     let isDisabled = this.slider.disabled;
285
     * }
286
     * ```
26✔
287
     */
7✔
288
    @Input()
289
    public get disabled(): boolean {
19✔
290
        return this._disabled;
291
    }
19✔
292

19✔
293
    /**
294
     * An @Input property that disables or enables UI interaction.
295
     * ```html
296
     * <igx-slider #slider [disabled]="'true'" [(ngModel)]="task.percentCompleted" [step]="5" [lowerBound]="20">
297
     * ```
298
     */
299
    public set disabled(disable: boolean) {
300
        this._disabled = disable;
301

302
        if (this._hasViewInit) {
303
            this.changeThumbFocusableState(disable);
304
        }
305
    }
306

307
    /**
2,159✔
308
     * Returns if the {@link IgxSliderComponent} is set as continuous.
1,127✔
309
     * ```typescript
310
     * @ViewChild("slider2")
311
     * public slider: IgxSliderComponent;
312
     * ngAfterViewInit(){
313
     *     let continuous = this.slider.continuous;
314
     * }
1,032✔
315
     * ```
316
     */
317
    @Input()
318
    public get continuous(): boolean {
80✔
319
        return this._continuous;
80✔
320
    }
71✔
321

71✔
322
    /**
323
     * An @Input property that marks the {@link IgxSliderComponent} as continuous.
324
     * By default is considered that the {@link IgxSliderComponent} is discrete.
325
     * Discrete {@link IgxSliderComponent} slider has step indicators over the track and visible thumb labels during interaction.
1,657✔
326
     * Continuous {@link IgxSliderComponent} does not have ticks and does not show bubble labels for values.
600✔
327
     * ```html
328
     * <igx-slider #slider [continuous]="'true'" [(ngModel)]="task.percentCompleted" [step]="5" [lowerBound]="20">
1,057✔
329
     * ```
330
     */
331
    public set continuous(continuous: boolean) {
332
        this._continuous = continuous;
333
        if (this._hasViewInit) {
334
            this.setTickInterval();
335
        }
336
    }
337

338
    /**
17✔
339
     * Returns the minimal displayed track value of the `IgxSliderComponent`.
11✔
340
     * ```typescript
341
     *  @ViewChild("slider2")
6✔
342
     * public slider: IgxSliderComponent;
343
     * ngAfterViewInit(){
344
     *     let sliderMin = this.slider.minValue;
1,014✔
345
     * }
346
     * ```
347
     */
348
    public get minValue(): number {
349
        if (this.labelsViewEnabled) {
350
            return 0;
351
        }
352

353
        return this._minValue;
354
    }
16✔
355

11✔
356
    /**
357
     * Sets the minimal displayed track value for the `IgxSliderComponent`.
5✔
358
     * The default minimal value is 0.
359
     * ```html
360
     * <igx-slider [type]="sliderType" [minValue]="56" [maxValue]="100">
361
     * ```
362
     */
363
    @Input()
1,582✔
364
    public set minValue(value: number) {
365
        if (value >= this.maxValue) {
366
            return;
367
        } else {
80✔
368
            this._minValue = value;
80✔
369
        }
80✔
370

80✔
371
        if (value > this._upperBound) {
80✔
372
            this.updateUpperBoundAndMaxTravelZone();
80✔
373
            this.lowerBound = value;
80✔
374
        }
80✔
375

80✔
376
        // Refresh min travel zone limit.
80✔
377
        this._pMin = 0;
80✔
378
        // Recalculate step distance.
80✔
379
        this.positionHandlersAndUpdateTrack();
80✔
380
        if (this._hasViewInit) {
80✔
381
            this.stepDistance = this.calculateStepDistance();
80✔
382
            this.setTickInterval();
80✔
383
        }
80✔
384
    }
80✔
385

80✔
386
    /**
80✔
387
     * Returns the maximum displayed track value for the {@link IgxSliderComponent}.
388
     * ```typescript
389
     * @ViewChild("slider")
390
     * public slider: IgxSliderComponent;
80✔
391
     * ngAfterViewInit(){
392
     *     let sliderMax = this.slider.maxValue;
80✔
393
     * }
80✔
394
     *  ```
395
     */
80✔
396
    public get maxValue(): number {
80✔
397
        return this.labelsViewEnabled ?
80✔
398
            this.labels.length - 1 :
80✔
399
            this._maxValue;
80✔
400
    }
80✔
401

80✔
402
    /**
403
     * Sets the maximal displayed track value for the `IgxSliderComponent`.
80✔
404
     * The default maximum value is 100.
80✔
405
     * ```html
80✔
406
     * <igx-slider [type]="sliderType" [minValue]="56" [maxValue]="256">
80✔
407
     * ```
80✔
408
     */
80✔
409
    @Input()
80✔
410
    public set maxValue(value: number) {
80✔
411
        if (value <= this._minValue) {
80✔
412
            return;
413
        } else {
414
            this._maxValue = value;
6✔
415
        }
6!
416

×
417
        if (value < this._lowerBound) {
418
            this.updateLowerBoundAndMinTravelZone();
6✔
419
            this.upperBound = value;
6✔
420
        }
6✔
421

6✔
422
        // refresh max travel zone limits.
423
        this._pMax = 1;
424
        // recalculate step distance.
5!
425
        this.positionHandlersAndUpdateTrack();
×
426
        if (this._hasViewInit) {
427
            this.stepDistance = this.calculateStepDistance();
5✔
428
            this.setTickInterval();
5✔
429
        }
5✔
430
    }
5✔
431

432
    /**
433
     * Returns the lower boundary of settable values of the `IgxSliderComponent`.
×
434
     * If not set, will return `minValue`.
435
     * ```typescript
436
     * @ViewChild("slider")
×
437
     * public slider: IgxSliderComponent;
438
     * ngAfterViewInit(){
439
     *     let sliderLowBound = this.slider.lowerBound;
440
     * }
441
     * ```
442
     */
443
    public get lowerBound(): number {
444
        if (!Number.isNaN(this._lowerBound) && this._lowerBound !== undefined) {
445
            return this.valueInRange(this._lowerBound, this.minValue, this.maxValue);
446
        }
447

448
        return this.minValue;
449
    }
4,395✔
450

451
    /**
452
     * Sets the lower boundary of settable values of the `IgxSliderComponent`.
453
     * If not set is the same as min value.
454
     * ```html
455
     * <igx-slider [step]="5" [lowerBound]="20">
456
     * ```
457
     */
458
    @Input()
459
    public set lowerBound(value: number) {
460
        if (value >= this.upperBound || (this.labelsViewEnabled && value < 0)) {
461
            return;
462
        }
1,946✔
463

916✔
464
        this._lowerBound = this.valueInRange(value, this.minValue, this.maxValue);
465

1,030✔
466
        // Refresh min travel zone.
467
        this._pMin = this.valueToFraction(this._lowerBound, 0, 1);
468
        this.positionHandlersAndUpdateTrack();
21✔
469
    }
21✔
470

20✔
471
    /**
20✔
472
     * Returns the upper boundary of settable values of the `IgxSliderComponent`.
20✔
473
     * If not set, will return `maxValue`
474
     * ```typescript
475
     * @ViewChild("slider")
476
     * public slider: IgxSliderComponent;
477
     * ngAfterViewInit(){
478
     *    let sliderUpBound = this.slider.upperBound;
479
     * }
480
     * ```
481
     */
482
    public get upperBound(): number {
483
        if (!Number.isNaN(this._upperBound) && this._upperBound !== undefined) {
484
            return this.valueInRange(this._upperBound, this.minValue, this.maxValue);
485
        }
486

487
        return this.maxValue;
3,775✔
488
    }
2,920✔
489

490
    /**
855✔
491
     * Sets the upper boundary of the `IgxSliderComponent`.
492
     * If not set is the same as max value.
493
     * ```html
7✔
494
     * <igx-slider [step]="5" [upperBound]="20">
7✔
495
     * ```
5✔
496
     */
5✔
497
    @Input()
5✔
498
    public set upperBound(value: number) {
499
        if (value <= this.lowerBound || (this.labelsViewEnabled && value > this.labels.length - 1)) {
500
            return;
501
        }
502

503
        this._upperBound = this.valueInRange(value, this.minValue, this.maxValue);
504
        // Refresh time travel zone.
505
        this._pMax = this.valueToFraction(this._upperBound, 0, 1);
506
        this.positionHandlersAndUpdateTrack();
507
    }
508

509
    /**
432✔
510
     * Returns the slider value. If the slider is of type {@link IgxSliderType.SLIDER} the returned value is number.
511
     * If the slider type is {@link IgxSliderType.RANGE}.
512
     * The returned value represents an object of {@link lowerValue} and {@link upperValue}.
513
     * ```typescript
514
     * @ViewChild("slider2")
515
     * public slider: IgxSliderComponent;
516
     * public sliderValue(event){
517
     *     let sliderVal = this.slider.value;
518
     * }
519
     * ```
520
     */
1,175✔
521
    public get value(): number | IRangeSliderValue {
522
        if (this.isRange) {
523
            return {
524
                lower: this.valueInRange(this.lowerValue, this.lowerBound, this.upperBound),
525
                upper: this.valueInRange(this.upperValue, this.lowerBound, this.upperBound)
526
            };
527
        } else {
528
            return this.valueInRange(this.upperValue, this.lowerBound, this.upperBound);
529
        }
530
    }
531

532
    /**
25,822✔
533
     * Sets the slider value.
534
     * If the slider is of type {@link IgxSliderType.SLIDER}.
535
     * The argument is number. By default the {@link value} gets the {@link lowerBound}.
536
     * If the slider type is {@link IgxSliderType.RANGE} the argument
537
     * represents an object of {@link lowerValue} and {@link upperValue} properties.
538
     * By default the object is associated with the {@link lowerBound} and {@link upperBound} property values.
71✔
539
     * ```typescript
540
     * rangeValue = {
541
     *   lower: 30,
542
     *   upper: 60
543
     * };
544
     * ```
545
     * ```html
71✔
546
     * <igx-slider [type]="sliderType" [(ngModel)]="rangeValue" [minValue]="56" [maxValue]="256">
547
     * ```
548
     */
549
    @Input()
550
    public set value(value: number | IRangeSliderValue) {
551
        this.normalizeByStep(value);
552

77✔
553
        if (this._hasViewInit) {
554
            this.setValue(this._value, true);
12✔
555
            this.positionHandlersAndUpdateTrack();
12✔
556
        }
557
    }
77!
558

×
559
    /**
560
     * Returns the number of the presented primary ticks.
561
     * ```typescript
562
     * const primaryTicks = this.slider.primaryTicks;
563
     * ```
564
     */
565
    @Input()
566
    public get primaryTicks() {
567
        if (this.labelsViewEnabled) {
568
            return this._primaryTicks = this.labels.length;
80✔
569
        }
59✔
570
        return this._primaryTicks;
571
    }
572

80✔
573
    /**
80!
574
     * Sets the number of primary ticks. If {@link @labels} is enabled, this property won't function.
575
     * Insted enable ticks by {@link showTicks} property.
576
     * ```typescript
80✔
577
     * this.slider.primaryTicks = 5;
578
     * ```
579
     */
580
    public set primaryTicks(val: number) {
581
        if (val <= 1) {
582
            return;
80✔
583
        }
80✔
584

80✔
585
        this._primaryTicks = val;
80✔
586
    }
80✔
587

80✔
588
    /**
80✔
589
     * Returns the number of the presented secondary ticks.
80✔
590
     * ```typescript
11✔
591
     * const secondaryTicks = this.slider.secondaryTicks;
11✔
592
     * ```
11✔
593
     */
11✔
594
    @Input()
595
    public get secondaryTicks() {
80✔
596
        return this._secondaryTicks;
11✔
597
    }
11✔
598

599
    /**
80✔
600
     * Sets the number of secondary ticks. The property functions even when {@link labels} is enabled,
80✔
601
     * but all secondary ticks won't present any tick labels.
4✔
602
     * ```typescript
603
     * this.slider.secondaryTicks = 5;
604
     * ```
605
     */
606
    public set secondaryTicks(val: number) {
607
        if (val < 1) {
608
            return;
609
        }
80✔
610

80✔
611
        this._secondaryTicks = val;
80✔
612
    }
80✔
613

614
    /**
615
     * Show/hide slider ticks
616
     * ```html
617
     * <igx-slier [showTicks]="true" [primaryTicks]="5"></igx-slier>
618
     * ```
5✔
619
     */
1✔
620
    @Input()
621
    public showTicks = false;
4✔
622

4✔
623
    /**
4✔
624
     * show/hide primary tick labels
625
     * ```html
626
     * <igx-slider [primaryTicks]="5" [primaryTickLabels]="false"></igx-slider>
627
     * ```
628
     */
629
    @Input()
3✔
630
    public primaryTickLabels = true;
631

632
    /**
633
     * show/hide secondary tick labels
634
     * ```html
635
     * <igx-slider [secondaryTicks]="5" [secondaryTickLabels]="false"></igx-slider>
3✔
636
     * ```
637
     */
638
    @Input()
639
    public secondaryTickLabels = true;
2✔
640

641
    /**
642
     * Changes ticks orientation:
643
     * bottom - The default orienation, below the slider track.
644
     * top - Above the slider track
645
     * mirror - combines top and bottom orientation.
646
     * ```html
6!
647
     * <igx-slider [primaryTicks]="5" [ticksOrientation]="ticksOrientation"></igx-slider>
×
648
     * ```
649
     */
650
    @Input()
6✔
651
    public ticksOrientation: TicksOrientation = TicksOrientation.Bottom;
652

653
    /**
6✔
654
     * Changes tick labels rotation:
6✔
655
     * horizontal - The default rotation
656
     * toptobottom - Rotates tick labels vertically to 90deg
657
     * bottomtotop - Rotate tick labels vertically to -90deg
658
     * ```html
659
     * <igx-slider [primaryTicks]="5" [secondaryTicks]="3" [tickLabelsOrientation]="tickLabelsOrientaiton"></igx-slider>
660
     * ```
37✔
661
     */
37✔
662
    @Input()
17✔
663
    public tickLabelsOrientation: TickLabelsOrientation = TickLabelsOrientation.Horizontal;
11!
664

×
665
    /**
666
     * @hidden
11✔
667
     */
668
    public get deactivateThumbLabel() {
669
        return ((this.primaryTicks && this.primaryTickLabels) || (this.secondaryTicks && this.secondaryTickLabels)) &&
6!
670
            (this.ticksOrientation === TicksOrientation.Top || this.ticksOrientation === TicksOrientation.Mirror);
×
671
    }
672

6✔
673
    /**
674
     * This event is emitted every time the value is changed.
17✔
675
     * ```typescript
676
     * public change(event){
677
     *    alert("The value has been changed!");
678
     * }
679
     * ```
680
     * ```html
681
     * <igx-slider (valueChange)="change($event)" #slider [(ngModel)]="task.percentCompleted" [step]="5">
682
     * ```
17✔
683
     */
684
    @Output()
685
    public valueChange = new EventEmitter<ISliderValueChangeEventArgs>();
20✔
686

20✔
687
    /**
13✔
688
     * This event is emitted every time the lower value of a range slider is changed.
689
     * ```typescript
690
     * public change(value){
37✔
691
     *    alert(`The lower value has been changed to ${value}`);
26✔
692
     * }
693
     * ```
694
     * ```html
695
     * <igx-slider [(lowerValue)]="model.lowervalue" (lowerValueChange)="change($event)" [step]="5">
696
     * ```
697
     */
698
     @Output()
32✔
699
     public lowerValueChange = new EventEmitter<number>();
700

701
     /**
702
      * This event is emitted every time the upper value of a range slider is changed.
703
      * ```typescript
704
      * public change(value){
×
705
      *    alert(`The upper value has been changed to ${value}`);
706
      * }
707
      * ```
708
      * ```html
160✔
709
      * <igx-slider [(upperValue)]="model.uppervalue" (upperValueChange)="change($event)" [step]="5">
91✔
710
      * ```
91✔
711
      */
81✔
712
      @Output()
81✔
713
      public upperValueChange = new EventEmitter<number>();
714

715
    /**
716
     * This event is emitted at the end of every slide interaction.
69✔
717
     * ```typescript
69✔
718
     * public change(event){
69✔
719
     *    alert("The value has been changed!");
69✔
720
     * }
721
     * ```
160✔
722
     * ```html
76✔
723
     * <igx-slider (dragFinished)="change($event)" #slider [(ngModel)]="task.percentCompleted" [step]="5">
724
     * ```
725
     */
726
    @Output()
69✔
727
    public dragFinished = new EventEmitter<number | IRangeSliderValue>();
3✔
728

3✔
729
    /**
3✔
730
     * @hidden
731
     */
69✔
732
    @ViewChild('ticks', { static: true })
3✔
733
    private ticks: ElementRef;
734

69✔
735
    /**
2✔
736
     * @hidden
737
     */
69✔
738
    @ViewChildren(IgxSliderThumbComponent)
739
    private thumbs: QueryList<IgxSliderThumbComponent> = new QueryList<IgxSliderThumbComponent>();
740

6✔
741
    /**
2✔
742
     * @hidden
743
     */
744
    @ViewChildren(IgxThumbLabelComponent)
4✔
745
    private labelRefs: QueryList<IgxThumbLabelComponent> = new QueryList<IgxThumbLabelComponent>();
746

6✔
747
    /**
748
     * @hidden
749
     */
1✔
750
    public onPan: Subject<number> = new Subject<number>();
1✔
751

752
    /**
753
     * @hidden
1✔
754
     */
1✔
755
    public stepDistance: number;
756

757
    // Limit handle travel zone
124✔
758
    private _pMin = 0;
759
    private _pMax = 1;
760

761
    // From/upperValue in percent values
762
    private _hasViewInit = false;
763
    private _minValue = 0;
764
    private _maxValue = 100;
×
765
    private _lowerBound: number;
7,312✔
766
    private _upperBound: number;
767
    private _lowerValue: number;
768
    private _upperValue: number;
398✔
769
    private _continuous = false;
398✔
770
    private _disabled = false;
398✔
771
    private _step = 1;
333✔
772
    private _value: number | IRangeSliderValue = 0;
773

398✔
774
    // ticks
333✔
775
    private _primaryTicks = 0;
776
    private _secondaryTicks = 0;
777

778
    private _labels = new Array<number | string | boolean | null | undefined>();
286✔
779
    private _type: IgxSliderType = IgxSliderType.SLIDER;
196✔
780

781
    private _destroyer$ = new Subject<boolean>();
782
    private _indicatorsDestroyer$ = new Subject<boolean>();
90✔
783
    private _indicatorsTimer: Observable<any>;
90✔
784

785
    private _onChangeCallback: (_: any) => void = noop;
286✔
786
    private _onTouchedCallback: () => void = noop;
238✔
787

788
    constructor(private renderer: Renderer2,
789
                private _el: ElementRef,
790
                private _cdr: ChangeDetectorRef,
2✔
791
                private _ngZone: NgZone,
2✔
792
                private _dir: IgxDirectionality) {
2✔
793
        this.stepDistance = this._step;
2✔
794
    }
2!
795

×
796
    /**
797
     * @hidden
2!
798
     */
×
799
    @HostListener('pointerdown', ['$event'])
800
    public onPointerDown($event: PointerEvent) {
2!
801
        this.findClosestThumb($event);
2✔
802

803
        if (!this.thumbTo.isActive && this.thumbFrom === undefined) {
804
            return;
×
805
        }
806

807
        const activeThumb = this.thumbTo.isActive ? this.thumbTo : this.thumbFrom;
808
        activeThumb.nativeElement.setPointerCapture($event.pointerId);
809
        this.showSliderIndicators();
121✔
810

121✔
811
        $event.preventDefault();
812
    }
32✔
813

814

815
    /**
89✔
816
     * @hidden
89✔
817
     */
818
    @HostListener('pointerup', ['$event'])
819
    public onPointerUp($event: PointerEvent) {
820
        if (!this.thumbTo.isActive && this.thumbFrom === undefined) {
121✔
821
            return;
121✔
822
        }
823

824
        const activeThumb = this.thumbTo.isActive ? this.thumbTo : this.thumbFrom;
38!
825
        activeThumb.nativeElement.releasePointerCapture($event.pointerId);
×
826

827
        this.hideSliderIndicators();
38✔
828
        this.dragFinished.emit(this.value);
23✔
829
    }
23✔
830

831
    /**
38✔
832
     * @hidden
38✔
833
     */
38✔
834
    @HostListener('focus')
18✔
835
    public onFocus() {
836
        this.toggleSliderIndicators();
38✔
837
    }
18✔
838

839
    /**
840
     * @hidden
841
     */
37!
842
    @HostListener('pan', ['$event'])
×
843
    public onPanListener($event) {
844
        this.update($event.srcEvent.clientX);
37✔
845
    }
37✔
846

4✔
847
    /**
4✔
848
     * Returns whether the `IgxSliderComponent` type is RANGE.
4✔
849
     * ```typescript
2✔
850
     *  @ViewChild("slider")
851
     * public slider: IgxSliderComponent;
4✔
852
     * ngAfterViewInit(){
2✔
853
     *     let sliderRange = this.slider.isRange;
854
     * }
855
     * ```
856
     */
857
    public get isRange(): boolean {
32✔
858
        return this.type === IgxSliderType.RANGE;
32✔
859
    }
860

861
    /**
92✔
862
     * Returns the lower value of a RANGE `IgxSliderComponent`.
92✔
863
     * ```typescript
31✔
864
     * @ViewChild("slider")
865
     * public slider: IgxSliderComponent;
92✔
866
     * public lowValue(event){
92✔
867
     *    let sliderLowValue = this.slider.lowerValue;
868
     * }
869
     * ```
2!
870
     */
871
    public get lowerValue(): number {
2,116✔
872
        if (!Number.isNaN(this._lowerValue) && this._lowerValue !== undefined && this._lowerValue >= this.lowerBound) {
1,172✔
873
            return this._lowerValue;
874
        }
875

876
        return this.lowerBound;
877
    }
878

879
    /**
880
     * Sets the lower value of a RANGE `IgxSliderComponent`.
89✔
881
     * ```typescript
50✔
882
     * @ViewChild("slider")
883
     * public slider: IgxSliderComponent;
884
     * public lowValue(event){
885
     *    this.slider.lowerValue = value;
886
     * }
887
     * ```
39✔
888
     */
889
    @Input()
890
    public set lowerValue(value: number) {
891
        const adjustedValue = this.valueInRange(value, this.lowerBound, this.upperBound);
250✔
892
        if (this._lowerValue !== adjustedValue) {
250✔
893
            this._lowerValue = adjustedValue;
250✔
894
            this.lowerValueChange.emit(this._lowerValue);
250✔
895
            this.value = { lower: this._lowerValue, upper: this._upperValue };
250✔
896
        }
96✔
897
    }
83✔
898

899
    /**
96✔
900
     * Returns the upper value of a RANGE `IgxSliderComponent`.
96✔
901
     * Returns `value` of a SLIDER `IgxSliderComponent`
902
     * ```typescript
903
     *  @ViewChild("slider2")
154✔
904
     * public slider: IgxSliderComponent;
905
     * public upperValue(event){
906
     *     let upperValue = this.slider.upperValue;
907
     * }
171✔
908
     * ```
61✔
909
     */
910
    public get upperValue() {
110✔
911
        if (!Number.isNaN(this._upperValue) && this._upperValue !== undefined && this._upperValue <= this.upperBound) {
912
            return this._upperValue;
37✔
913
        }
914

915
        return this.upperBound;
110✔
916
    }
917

918
    /**
37✔
919
     * Sets the upper value of a RANGE `IgxSliderComponent`.
37✔
920
     * ```typescript
921
     *  @ViewChild("slider2")
922
     * public slider: IgxSliderComponent;
37✔
923
     * public upperValue(event){
924
     *     this.slider.upperValue = value;
925
     * }
26✔
926
     * ```
927
     */
2✔
928
    @Input()
929
    public set upperValue(value: number) {
930
        const adjustedValue = this.valueInRange(value, this.lowerBound, this.upperBound);
931
        if (this._upperValue !== adjustedValue) {
932
            this._upperValue = adjustedValue;
933
            this.upperValueChange.emit(this._upperValue);
934
            this.value = { lower: this._lowerValue, upper: this._upperValue };
2✔
935
        }
936
    }
937

938
    /**
939
     * Returns the value corresponding the lower label.
940
     * ```typescript
941
     * @ViewChild("slider")
942
     * public slider: IgxSliderComponent;
943
     * let label = this.slider.lowerLabel;
944
     * ```
945
     */
946
    public get lowerLabel() {
947
        return this.labelsViewEnabled ? this.labels[this.lowerValue] : this.lowerValue;
948
    }
949

950
    /**
951
     * Returns the value corresponding the upper label.
952
     * ```typescript
953
     * @ViewChild("slider")
954
     * public slider: IgxSliderComponent;
955
     * let label = this.slider.upperLabel;
956
     * ```
957
     */
958
    public get upperLabel() {
959
        return this.labelsViewEnabled ? this.labels[this.upperValue] : this.upperValue;
960
    }
961

962
    /**
963
     * Returns if label view is enabled.
964
     * If the {@link labels} is set, the view is automatically activated.
965
     * ```typescript
966
     * @ViewChild("slider")
967
     * public slider: IgxSliderComponent;
968
     * let labelView = this.slider.labelsViewEnabled;
969
     * ```
970
     */
971
    public get labelsViewEnabled(): boolean {
972
        return !!(this.labels && this.labels.length > 1);
973
    }
974

975
    /**
976
     * @hidden
977
     */
978
    public get showTopTicks() {
979
        return this.ticksOrientation === TicksOrientation.Top ||
2✔
980
            this.ticksOrientation === TicksOrientation.Mirror;
981
    }
982

983
    /**
984
     * @hidden
985
     */
986
    public get showBottomTicks() {
987
        return this.ticksOrientation === TicksOrientation.Bottom ||
988
            this.ticksOrientation === TicksOrientation.Mirror;
989
    }
990

991
    /**
992
     * @hidden
993
     */
994
    public ngOnChanges(changes: SimpleChanges) {
995
        if (changes.minValue && changes.maxValue &&
996
            changes.minValue.currentValue < changes.maxValue.currentValue) {
997
            this._maxValue = changes.maxValue.currentValue;
998
            this._minValue = changes.minValue.currentValue;
999
        }
1000

1001
        if (changes.step && changes.step.isFirstChange()) {
1002
            this.normalizeByStep(this._value);
1003
        }
1004
    }
1005

1006
    /**
1007
     * @hidden
1008
     */
1009
    public ngOnInit() {
1010
        /**
1011
         * if {@link SliderType.SLIDER} than the initial value shold be the lowest one.
1012
         */
1013
        if (!this.isRange) {
1014
            this._upperValue = this.lowerBound;
1015
        }
1016

1017
        // Set track travel zone
1018
        this._pMin = this.valueToFraction(this.lowerBound) || 0;
1019
        this._pMax = this.valueToFraction(this.upperBound) || 1;
1020
    }
1021

1022
    public ngAfterContentInit() {
1023
        this.setValue(this._value, false);
1024
    }
1025

1026
    /**
1027
     * @hidden
1028
     */
1029
    public ngAfterViewInit() {
1030
        this._hasViewInit = true;
1031
        this.stepDistance = this.calculateStepDistance();
1032
        this.positionHandlersAndUpdateTrack();
1033
        this.setTickInterval();
1034
        this.changeThumbFocusableState(this.disabled);
1035

1036
        this.subscribeTo(this.thumbFrom, this.thumbChanged.bind(this));
1037
        this.subscribeTo(this.thumbTo, this.thumbChanged.bind(this));
1038

1039
        this.thumbs.changes.pipe(takeUntil(this._destroyer$)).subscribe(change => {
1040
            const thumbFrom = change.find((thumb: IgxSliderThumbComponent) => thumb.type === SliderHandle.FROM);
1041
            this.positionHandler(thumbFrom, null, this.lowerValue);
1042
            this.subscribeTo(thumbFrom, this.thumbChanged.bind(this));
1043
            this.changeThumbFocusableState(this.disabled);
1044
        });
1045

1046
        this.labelRefs.changes.pipe(takeUntil(this._destroyer$)).subscribe(() => {
1047
            const labelFrom = this.labelRefs.find((label: IgxThumbLabelComponent) => label.type === SliderHandle.FROM);
1048
            this.positionHandler(null, labelFrom, this.lowerValue);
1049
        });
1050

1051
        this._ngZone.runOutsideAngular(() => {
1052
            resizeObservable(this._el.nativeElement).pipe(
1053
                throttleTime(40),
1054
                takeUntil(this._destroyer$)).subscribe(() => this._ngZone.run(() => {
1055
                    this.stepDistance = this.calculateStepDistance();
1056
                }));
1057
        });
1058
    }
1059

1060
    /**
1061
     * @hidden
1062
     */
1063
    public ngOnDestroy() {
1064
        this._destroyer$.next(true);
1065
        this._destroyer$.complete();
1066

1067
        this._indicatorsDestroyer$.next(true);
1068
        this._indicatorsDestroyer$.complete();
1069
    }
1070

1071
    /**
1072
     * @hidden
1073
     */
1074
    public writeValue(value: IRangeSliderValue | number): void {
1075
        if (!value) {
1076
            return;
1077
        }
1078

1079
        this.normalizeByStep(value);
1080
        this.setValue(this._value, false);
1081
        this.positionHandlersAndUpdateTrack();
1082
    }
1083

1084
    /**
1085
     * @hidden
1086
     */
1087
    public registerOnChange(fn: any): void {
1088
        this._onChangeCallback = fn;
1089
    }
1090

1091
    /**
1092
     * @hidden
1093
     */
1094
    public registerOnTouched(fn: any): void {
1095
        this._onTouchedCallback = fn;
1096
    }
1097

1098
    /** @hidden */
1099
    public getEditElement() {
1100
        return this.isRange ? this.thumbFrom.nativeElement : this.thumbTo.nativeElement;
1101
    }
1102

1103
    /**
1104
     *
1105
     * @hidden
1106
     */
1107
    public update(mouseX) {
1108
        if (this.disabled) {
1109
            return;
1110
        }
1111

1112
        // Update To/From Values
1113
        this.onPan.next(mouseX);
1114

1115
        // Finally do positionHandlersAndUpdateTrack the DOM
1116
        // based on data values
1117
        this.positionHandlersAndUpdateTrack();
1118
        this._onTouchedCallback();
1119
    }
1120

1121
    /**
1122
     * @hidden
1123
     */
1124
    public thumbChanged(value: number, thumbType: string) {
1125
        const oldValue = this.value;
1126

1127
        if (this.isRange) {
1128
            if (thumbType === SliderHandle.FROM) {
1129
                if (this.lowerValue + value > this.upperValue) {
1130
                    this.upperValue = this.lowerValue + value;
1131
                }
1132
                this.lowerValue += value;
1133
            } else {
1134
                if (this.upperValue + value < this.lowerValue) {
1135
                    this.lowerValue = this.upperValue + value;
1136
                }
1137
                this.upperValue += value;
1138
            }
1139

1140
            const newVal: IRangeSliderValue = {
1141
                lower: this.lowerValue,
1142
                upper: this.upperValue
1143
            }
1144

1145
            // Swap the thumbs if a collision appears.
1146
            // if (newVal.lower == newVal.upper) {
1147
            //     this.toggleThumb();
1148
            // }
1149

1150
            this.value = newVal;
1151

1152
        } else {
1153
            const newVal = (this.value as number) + value;
1154
            if (newVal >= this.lowerBound && newVal <= this.upperBound) {
1155
                this.value = newVal;
1156
            }
1157
        }
1158

1159
        if (this.hasValueChanged(oldValue)) {
1160
            this.emitValueChange(oldValue);
1161
        }
1162
    }
1163

1164
    /**
1165
     * @hidden
1166
     */
1167
    public onThumbChange() {
1168
        this.toggleSliderIndicators();
1169
    }
1170

1171
    /**
1172
     * @hidden
1173
     */
1174
    public onHoverChange(state: boolean) {
1175
        return state ? this.showSliderIndicators() : this.hideSliderIndicators();
1176
    }
1177

1178
    public setValue(value: number | IRangeSliderValue, triggerChange: boolean) {
1179
        let res;
1180
        if (!this.isRange) {
1181
            value = value as number;
1182
            if (!isNaN(value)) {
1183
                this._upperValue = value - value % this.step;
1184
                res = this.upperValue;
1185
            }
1186
        } else {
1187
            value = this.validateInitialValue(value as IRangeSliderValue);
1188
            this._upperValue = value.upper;
1189
            this._lowerValue = value.lower;
1190
            res = { lower: this.lowerValue, upper: this.upperValue };
1191
        }
1192

1193
        if (triggerChange) {
1194
            this._onChangeCallback(res);
1195
        }
1196
    }
1197

1198
    private validateInitialValue(value: IRangeSliderValue) {
1199
        if (value.upper < value.lower) {
1200
            const temp = value.upper;
1201
            value.upper = value.lower;
1202
            value.lower = temp;
1203
        }
1204

1205
        if (value.lower < this.lowerBound) {
1206
            value.lower = this.lowerBound;
1207
        }
1208

1209
        if (value.upper > this.upperBound) {
1210
            value.upper = this.upperBound;
1211
        }
1212

1213
        return value;
1214
    }
1215

1216
    private findClosestThumb(event: PointerEvent) {
1217
        if (this.isRange) {
1218
            this.closestHandle(event);
1219
        } else {
1220
            this.thumbTo.nativeElement.focus();
1221
        }
1222

1223
        this.update(event.clientX);
1224
    }
1225

1226
    private updateLowerBoundAndMinTravelZone() {
1227
        this.lowerBound = this.minValue;
1228
        this._pMin = 0;
1229
    }
1230

1231
    private updateUpperBoundAndMaxTravelZone() {
1232
        this.upperBound = this.maxValue;
1233
        this._pMax = 1;
1234
    }
1235

1236
    private calculateStepDistance() {
1237
        return this._el.nativeElement.getBoundingClientRect().width / (this.maxValue - this.minValue) * this.step;
1238
    }
1239

1240
    // private toggleThumb() {
1241
    //     return this.thumbFrom.isActive ?
1242
    //         this.thumbTo.nativeElement.focus() :
1243
    //         this.thumbFrom.nativeElement.focus();
1244
    // }
1245

1246
    private valueInRange(value, min = 0, max = 100) {
1247
        return Math.max(Math.min(value, max), min);
1248
    }
1249

1250
    private positionHandler(thumbHandle: ElementRef, labelHandle: ElementRef, position: number) {
1251
        const percent = `${this.valueToFraction(position) * 100}%`;
1252
        const dir = this._dir.rtl ? 'right' : 'left';
1253

1254
        if (thumbHandle) {
1255
            thumbHandle.nativeElement.style[dir] = percent;
1256
        }
1257

1258
        if (labelHandle) {
1259
            labelHandle.nativeElement.style[dir] = percent;
1260
        }
1261
    }
1262

1263
    private positionHandlersAndUpdateTrack() {
1264
        if (!this.isRange) {
1265
            this.positionHandler(this.thumbTo, this.labelTo, this.value as number);
1266
        } else {
1267
            this.positionHandler(this.thumbTo, this.labelTo, (this.value as IRangeSliderValue).upper);
1268
            this.positionHandler(this.thumbFrom, this.labelFrom, (this.value as IRangeSliderValue).lower);
1269
        }
1270

1271
        if (this._hasViewInit) {
1272
            this.updateTrack();
1273
        }
1274
    }
1275

1276
    private closestHandle(event: PointerEvent) {
1277
        const fromOffset = this.thumbFrom.nativeElement.offsetLeft + this.thumbFrom.nativeElement.offsetWidth / 2;
1278
        const toOffset = this.thumbTo.nativeElement.offsetLeft + this.thumbTo.nativeElement.offsetWidth / 2;
1279
        const xPointer = event.clientX - this._el.nativeElement.getBoundingClientRect().left;
1280
        const match = this.closestTo(xPointer, [fromOffset, toOffset]);
1281

1282
        if (fromOffset === toOffset && toOffset < xPointer) {
1283
            this.thumbTo.nativeElement.focus();
1284
        } else if (fromOffset === toOffset && toOffset > xPointer) {
1285
            this.thumbFrom.nativeElement.focus();
1286
        } else if (match === fromOffset) {
1287
            this.thumbFrom.nativeElement.focus();
1288
        } else {
1289
            this.thumbTo.nativeElement.focus();
1290
        }
1291
    }
1292

1293
    private setTickInterval() {
1294
        let interval;
1295
        const trackProgress = 100;
1296

1297
        if (this.labelsViewEnabled) {
1298
            // Calc ticks depending on the labels length;
1299
            interval = ((trackProgress / (this.labels.length - 1) * 10)) / 10;
1300
        } else {
1301
            const trackRange = this.maxValue - this.minValue;
1302
            interval = this.step > 1 ?
1303
                (trackProgress / ((trackRange / this.step)) * 10) / 10
1304
                : null;
1305
        }
1306

1307
        this.renderer.setStyle(this.ticks.nativeElement, 'stroke-dasharray', `0, ${interval * Math.sqrt(2)}%`);
1308
        this.renderer.setStyle(this.ticks.nativeElement, 'visibility', this.continuous || interval === null ? 'hidden' : 'visible');
1309
    }
1310

1311
    private showSliderIndicators() {
1312
        if (this.disabled) {
1313
            return;
1314
        }
1315

1316
        if (this._indicatorsTimer) {
1317
            this._indicatorsDestroyer$.next(true);
1318
            this._indicatorsTimer = null;
1319
        }
1320

1321
        this.thumbTo.showThumbIndicators();
1322
        this.labelTo.active = true;
1323
        if (this.thumbFrom) {
1324
            this.thumbFrom.showThumbIndicators();
1325
        }
1326

1327
        if (this.labelFrom) {
1328
            this.labelFrom.active = true;
1329
        }
1330

1331
    }
1332

1333
    private hideSliderIndicators() {
1334
        if (this.disabled) {
1335
            return;
1336
        }
1337

1338
        this._indicatorsTimer = timer(this.thumbLabelVisibilityDuration);
1339
        this._indicatorsTimer.pipe(takeUntil(this._indicatorsDestroyer$)).subscribe(() => {
1340
            this.thumbTo.hideThumbIndicators();
1341
            this.labelTo.active = false;
1342
            if (this.thumbFrom) {
1343
                this.thumbFrom.hideThumbIndicators();
1344
            }
1345

1346
            if (this.labelFrom) {
1347
                this.labelFrom.active = false;
1348
            }
1349
        });
1350
    }
1351

1352
    private toggleSliderIndicators() {
1353
        this.showSliderIndicators();
1354
        this.hideSliderIndicators();
1355
    }
1356

1357
    private changeThumbFocusableState(state: boolean) {
1358
        const value = state ? -1 : 1;
1359

1360
        if (this.isRange) {
1361
            this.thumbFrom.tabindex = value;
1362
        }
1363

1364
        this.thumbTo.tabindex = value;
1365

1366
        this._cdr.detectChanges();
1367
    }
1368

1369
    private closestTo(goal: number, positions: number[]): number {
1370
        return positions.reduce((previous, current) => (Math.abs(goal - current) < Math.abs(goal - previous) ? current : previous));
1371
    }
1372

1373
    private valueToFraction(value: number, pMin = this._pMin, pMax = this._pMax) {
1374
        return this.valueInRange((value - this.minValue) / (this.maxValue - this.minValue), pMin, pMax);
1375
    }
1376

1377
    /**
1378
     * @hidden
1379
     * Normalizе the value when two-way data bind is used and {@link this.step} is set.
1380
     * @param value
1381
     */
1382
    private normalizeByStep(value: IRangeSliderValue | number) {
1383
        if (this.isRange) {
1384
            this._value = {
1385
                lower: (value as IRangeSliderValue).lower - ((value as IRangeSliderValue).lower % this.step),
1386
                upper: (value as IRangeSliderValue).upper - ((value as IRangeSliderValue).upper % this.step)
1387
            };
1388
        } else {
1389
            this._value = (value as number) - ((value as number) % this.step);
1390
        }
1391
    }
1392

1393
    private updateTrack() {
1394
        const fromPosition = this.valueToFraction(this.lowerValue);
1395
        const toPosition = this.valueToFraction(this.upperValue);
1396
        const positionGap = toPosition - fromPosition;
1397

1398
        let trackLeftIndention = fromPosition;
1399
        if (this.isRange) {
1400
            if (positionGap) {
1401
                trackLeftIndention = Math.round((1 / positionGap * fromPosition) * 100);
1402
            }
1403

1404
            trackLeftIndention = this._dir.rtl ? -trackLeftIndention : trackLeftIndention;
1405
            this.renderer.setStyle(this.trackRef.nativeElement, 'transform', `scaleX(${positionGap}) translateX(${trackLeftIndention}%)`);
1406
        } else {
1407
            this.renderer.setStyle(this.trackRef.nativeElement, 'transform', `scaleX(${toPosition})`);
1408
        }
1409
    }
1410

1411
    private subscribeTo(thumb: IgxSliderThumbComponent, callback: (a: number, b: string) => void) {
1412
        if (!thumb) {
1413
            return;
1414
        }
1415

1416
        thumb.thumbValueChange
1417
            .pipe(takeUntil(this.unsubscriber(thumb)))
1418
            .subscribe(value => callback(value, thumb.type));
1419
    }
1420

1421
    private unsubscriber(thumb: IgxSliderThumbComponent) {
1422
        return merge(this._destroyer$, thumb.destroy);
1423
    }
1424

1425
    private hasValueChanged(oldValue) {
1426
        const isSliderWithDifferentValue: boolean = !this.isRange && oldValue !== this.value;
1427
        const isRangeWithOneDifferentValue: boolean = this.isRange &&
1428
            ((oldValue as IRangeSliderValue).lower !== (this.value as IRangeSliderValue).lower ||
1429
                (oldValue as IRangeSliderValue).upper !== (this.value as IRangeSliderValue).upper);
1430

1431
        return isSliderWithDifferentValue || isRangeWithOneDifferentValue;
1432
    }
1433

1434
    private emitValueChange(oldValue: number | IRangeSliderValue) {
1435
        this.valueChange.emit({ oldValue, value: this.value });
1436
    }
1437
}
1438

1439
/**
1440
 * @hidden
1441
 */
1442

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc