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

IgniteUI / igniteui-angular / 12767264866

14 Jan 2025 12:06PM UTC coverage: 91.575% (-0.02%) from 91.595%
12767264866

Pull #15236

github

web-flow
Merge 28ced7988 into 58936f8fc
Pull Request #15236: chore(carousel): remove deprecated keyboardSupport

12974 of 15217 branches covered (85.26%)

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

5 existing lines in 1 file now uncovered.

26305 of 28725 relevant lines covered (91.58%)

34047.67 hits per line

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

92.81
/projects/igniteui-angular/src/lib/carousel/carousel.component.ts
1
import { NgIf, NgClass, NgFor, NgTemplateOutlet, DOCUMENT } from '@angular/common';
2
import {
3
    AfterContentInit,
4
    ChangeDetectorRef,
5
    Component,
6
    ContentChild,
7
    ContentChildren,
8
    ElementRef,
9
    EventEmitter,
10
    HostBinding,
11
    HostListener,
12
    Inject,
13
    Injectable,
14
    Input,
15
    IterableChangeRecord,
16
    IterableDiffer,
17
    IterableDiffers,
18
    OnDestroy,
19
    Output,
20
    QueryList,
21
    TemplateRef,
22
    ViewChild,
23
    ViewChildren,
24
    booleanAttribute
25
} from '@angular/core';
26
import { HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
27
import { merge, Subject } from 'rxjs';
28
import { takeUntil } from 'rxjs/operators';
29
import { CarouselResourceStringsEN, ICarouselResourceStrings } from '../core/i18n/carousel-resources';
30
import { first, IBaseEventArgs, last, PlatformUtil } from '../core/utils';
31
import { IgxAngularAnimationService } from '../services/animation/angular-animation-service';
32
import { AnimationService } from '../services/animation/animation';
33
import { Direction, IgxCarouselComponentBase } from './carousel-base';
34
import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives';
35
import { IgxSlideComponent } from './slide.component';
36
import { IgxIconComponent } from '../icon/icon.component';
37
import { IgxButtonDirective } from '../directives/button/button.directive';
38
import { getCurrentResourceStrings } from '../core/i18n/resources';
39
import { HammerGesturesManager } from '../core/touch';
40
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
41
import { IgxDirectionality } from '../services/direction/directionality';
42

43
let NEXT_ID = 0;
2✔
44

45

46
@Injectable()
47
export class CarouselHammerConfig extends HammerGestureConfig {
2✔
48
    public override overrides = {
×
49
        pan: { direction: HammerGesturesManager.Hammer?.DIRECTION_HORIZONTAL }
50
    };
51
}
52
/**
53
 * **Ignite UI for Angular Carousel** -
54
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel.html)
55
 *
56
 * The Ignite UI Carousel is used to browse or navigate through a collection of slides. Slides can contain custom
57
 * content such as images or cards and be used for things such as on-boarding tutorials or page-based interfaces.
58
 * It can be used as a separate fullscreen element or inside another component.
59
 *
60
 * Example:
61
 * ```html
62
 * <igx-carousel>
63
 *   <igx-slide>
64
 *     <h3>First Slide Header</h3>
65
 *     <p>First slide Content</p>
66
 *   <igx-slide>
67
 *   <igx-slide>
68
 *     <h3>Second Slide Header</h3>
69
 *     <p>Second Slide Content</p>
70
 * </igx-carousel>
71
 * ```
72
 */
73
@Component({
74
    providers: [
75
        {
76
            provide: HAMMER_GESTURE_CONFIG,
77
            useClass: CarouselHammerConfig
78
        }
79
    ],
80
    selector: 'igx-carousel',
81
    templateUrl: 'carousel.component.html',
82
    styles: [`
83
    :host {
84
        display: block;
85
        outline-style: none;
86
    }`],
87
    imports: [IgxButtonDirective, IgxIconComponent, NgIf, NgClass, NgFor, NgTemplateOutlet]
88
})
89

90
export class IgxCarouselComponent extends IgxCarouselComponentBase implements OnDestroy, AfterContentInit {
2✔
91

92
    /**
93
     * Sets the `id` of the carousel.
94
     * If not set, the `id` of the first carousel component will be `"igx-carousel-0"`.
95
     * ```html
96
     * <igx-carousel id="my-first-carousel"></igx-carousel>
97
     * ```
98
     *
99
     * @memberof IgxCarouselComponent
100
     */
101
    @HostBinding('attr.id')
102
    @Input()
103
    public id = `igx-carousel-${NEXT_ID++}`;
42✔
104
    /**
105
     * Returns the `role` attribute of the carousel.
106
     * ```typescript
107
     * let carouselRole =  this.carousel.role;
108
     * ```
109
     *
110
     * @memberof IgxCarouselComponent
111
     */
112
    @HostBinding('attr.role') public role = 'region';
42✔
113

114
    /** @hidden */
115
    @HostBinding('attr.aria-roledescription')
116
    public roleDescription = 'carousel';
42✔
117

118
    /** @hidden */
119
    @HostBinding('attr.aria-labelledby')
120
    public get labelId() {
121
        return this.showIndicatorsLabel ? `${this.id}-label` : null;
394✔
122
    }
123

124
    /** @hidden */
125
    @HostBinding('class.igx-carousel--vertical')
126
        public get isVertical(): boolean {
127
                return this.vertical;
392✔
128
        }
129

130
    /**
131
     * Returns the class of the carousel component.
132
     * ```typescript
133
     * let class =  this.carousel.cssClass;
134
     * ```
135
     *
136
     * @memberof IgxCarouselComponent
137
     */
138
    @HostBinding('class.igx-carousel')
139
    public cssClass = 'igx-carousel';
42✔
140

141
    /**
142
     * Gets the `touch-action` style of the `list item`.
143
     * ```typescript
144
     * let touchAction = this.listItem.touchAction;
145
     * ```
146
     */
147
    @HostBinding('style.touch-action')
148
    public get touchAction() {
149
        return this.gesturesSupport ? 'pan-y' : 'auto';
392✔
150
    }
151

152
    /**
153
     * Sets whether the carousel should `loop` back to the first slide after reaching the last slide.
154
     * Default value is `true`.
155
     * ```html
156
     * <igx-carousel [loop]="false"></igx-carousel>
157
     * ```
158
     *
159
     * @memberOf IgxCarouselComponent
160
     */
161
    @Input({ transform: booleanAttribute }) public loop = true;
42✔
162

163
    /**
164
     * Sets whether the carousel will `pause` the slide transitions on user interactions.
165
     * Default value is `true`.
166
     * ```html
167
     *  <igx-carousel [pause]="false"></igx-carousel>
168
     * ```
169
     *
170
     * @memberOf IgxCarouselComponent
171
     */
172
    @Input({ transform: booleanAttribute }) public pause = true;
42✔
173

174
    /**
175
     * Controls whether the carousel should render the left/right `navigation` buttons.
176
     * Default value is `true`.
177
     * ```html
178
     * <igx-carousel [navigation]="false"></igx-carousel>
179
     * ```
180
     *
181
     * @memberOf IgxCarouselComponent
182
     */
183
    @Input({ transform: booleanAttribute }) public navigation = true;
42✔
184

185
    /**
186
     * Controls whether the carousel should render the indicators.
187
     * Default value is `true`.
188
     * ```html
189
     * <igx-carousel [indicators]="false"></igx-carousel>
190
     * ```
191
     *
192
     * @memberOf IgxCarouselComponent
193
     */
194
    @Input({ transform: booleanAttribute }) public indicators = true;
42✔
195

196

197
    /**
198
     * Controls whether the carousel has vertical alignment.
199
     * Default value is `false`.
200
     * ```html
201
     * <igx-carousel [vertical]="true"></igx-carousel>
202
     * ```
203
     *
204
     * @memberOf IgxCarouselComponent
205
     */
206
    @Input({ transform: booleanAttribute }) public override vertical = false;
42✔
207

208
    /**
209
     * Controls whether the carousel should support gestures.
210
     * Default value is `true`.
211
     * ```html
212
     * <igx-carousel [gesturesSupport]="false"></igx-carousel>
213
     * ```
214
     *
215
     * @memberOf IgxCarouselComponent
216
     */
217
    @Input({ transform: booleanAttribute }) public gesturesSupport = true;
42✔
218

219
    /**
220
     * Controls the maximum indexes that can be shown.
221
     * Default value is `10`.
222
     * ```html
223
     * <igx-carousel [maximumIndicatorsCount]="5"></igx-carousel>
224
     * ```
225
     *
226
     * @memberOf IgxCarouselComponent
227
     */
228
    @Input() public maximumIndicatorsCount = 10;
42✔
229

230
    /**
231
     * Gets/sets the display mode of carousel indicators. It can be `start` or `end`.
232
     * Default value is `end`.
233
     * ```html
234
     * <igx-carousel indicatorsOrientation="start">
235
     * <igx-carousel>
236
     * ```
237
     *
238
     * @memberOf IgxCarouselComponent
239
     */
240
    @Input() public indicatorsOrientation: CarouselIndicatorsOrientation = CarouselIndicatorsOrientation.end;
42✔
241

242
    /**
243
     * Gets/sets the animation type of carousel.
244
     * Default value is `slide`.
245
     * ```html
246
     * <igx-carousel animationType="none">
247
     * <igx-carousel>
248
     * ```
249
     *
250
     * @memberOf IgxCarouselComponent
251
     */
252
    @Input() public override animationType: CarouselAnimationType = CarouselAnimationType.slide;
42✔
253

254
    /**
255
     * The custom template, if any, that should be used when rendering carousel indicators
256
     *
257
     * ```typescript
258
     * // Set in typescript
259
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
260
     * myComponent.carousel.indicatorTemplate = myCustomTemplate;
261
     * ```
262
     * ```html
263
     * <!-- Set in markup -->
264
     *  <igx-carousel #carousel>
265
     *      ...
266
     *      <ng-template igxCarouselIndicator let-slide>
267
     *         <igx-icon *ngIf="slide.active">brightness_7</igx-icon>
268
     *         <igx-icon *ngIf="!slide.active">brightness_5</igx-icon>
269
     *      </ng-template>
270
     *  </igx-carousel>
271
     * ```
272
     */
273
    @ContentChild(IgxCarouselIndicatorDirective, { read: TemplateRef, static: false })
274
    public indicatorTemplate: TemplateRef<any> = null;
42✔
275

276
    /**
277
     * The custom template, if any, that should be used when rendering carousel next button
278
     *
279
     * ```typescript
280
     * // Set in typescript
281
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
282
     * myComponent.carousel.nextButtonTemplate = myCustomTemplate;
283
     * ```
284
     * ```html
285
     * <!-- Set in markup -->
286
     *  <igx-carousel #carousel>
287
     *      ...
288
     *      <ng-template igxCarouselNextButton let-disabled>
289
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
290
     *              <igx-icon name="add"></igx-icon>
291
     *          </button>
292
     *      </ng-template>
293
     *  </igx-carousel>
294
     * ```
295
     */
296
    @ContentChild(IgxCarouselNextButtonDirective, { read: TemplateRef, static: false })
297
    public nextButtonTemplate: TemplateRef<any> = null;
42✔
298

299
    /**
300
     * The custom template, if any, that should be used when rendering carousel previous button
301
     *
302
     * ```typescript
303
     * // Set in typescript
304
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
305
     * myComponent.carousel.prevButtonTemplate = myCustomTemplate;
306
     * ```
307
     * ```html
308
     * <!-- Set in markup -->
309
     *  <igx-carousel #carousel>
310
     *      ...
311
     *      <ng-template igxCarouselPrevButton let-disabled>
312
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
313
     *              <igx-icon name="remove"></igx-icon>
314
     *          </button>
315
     *      </ng-template>
316
     *  </igx-carousel>
317
     * ```
318
     */
319
    @ContentChild(IgxCarouselPrevButtonDirective, { read: TemplateRef, static: false })
320
    public prevButtonTemplate: TemplateRef<any> = null;
42✔
321

322
    /**
323
     * The collection of `slides` currently in the carousel.
324
     * ```typescript
325
     * let slides: QueryList<IgxSlideComponent> = this.carousel.slides;
326
     * ```
327
     *
328
     * @memberOf IgxCarouselComponent
329
     */
330
    @ContentChildren(IgxSlideComponent)
331
    public slides: QueryList<IgxSlideComponent>;
332

333
    /**
334
     * An event that is emitted after a slide transition has happened.
335
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
336
     * ```html
337
     * <igx-carousel (slideChanged)="slideChanged($event)"></igx-carousel>
338
     * ```
339
     *
340
     * @memberOf IgxCarouselComponent
341
     */
342
    @Output() public slideChanged = new EventEmitter<ISlideEventArgs>();
42✔
343

344
    /**
345
     * An event that is emitted after a slide has been added to the carousel.
346
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
347
     * ```html
348
     * <igx-carousel (slideAdded)="slideAdded($event)"></igx-carousel>
349
     * ```
350
     *
351
     * @memberOf IgxCarouselComponent
352
     */
353
    @Output() public slideAdded = new EventEmitter<ISlideEventArgs>();
42✔
354

355
    /**
356
     * An event that is emitted after a slide has been removed from the carousel.
357
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
358
     * ```html
359
     * <igx-carousel (slideRemoved)="slideRemoved($event)"></igx-carousel>
360
     * ```
361
     *
362
     * @memberOf IgxCarouselComponent
363
     */
364
    @Output() public slideRemoved = new EventEmitter<ISlideEventArgs>();
42✔
365

366
    /**
367
     * An event that is emitted after the carousel has been paused.
368
     * Provides a reference to the `IgxCarouselComponent` as an event argument.
369
     * ```html
370
     * <igx-carousel (carouselPaused)="carouselPaused($event)"></igx-carousel>
371
     * ```
372
     *
373
     * @memberOf IgxCarouselComponent
374
     */
375
    @Output() public carouselPaused = new EventEmitter<IgxCarouselComponent>();
42✔
376

377
    /**
378
     * An event that is emitted after the carousel has resumed transitioning between `slides`.
379
     * Provides a reference to the `IgxCarouselComponent` as an event argument.
380
     * ```html
381
     * <igx-carousel (carouselPlaying)="carouselPlaying($event)"></igx-carousel>
382
     * ```
383
     *
384
     * @memberOf IgxCarouselComponent
385
     */
386
    @Output() public carouselPlaying = new EventEmitter<IgxCarouselComponent>();
42✔
387

388
    @ViewChild('defaultIndicator', { read: TemplateRef, static: true })
389
    private defaultIndicator: TemplateRef<any>;
390

391
    @ViewChild('defaultNextButton', { read: TemplateRef, static: true })
392
    private defaultNextButton: TemplateRef<any>;
393

394
    @ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
395
    private defaultPrevButton: TemplateRef<any>;
396

397
    @ViewChildren('indicators', { read: ElementRef })
398
    private _indicators: QueryList<ElementRef<HTMLDivElement>>;
399

400
    /**
401
     * @hidden
402
     * @internal
403
     */
404
    public stoppedByInteraction: boolean;
405
    protected override currentItem: IgxSlideComponent;
406
    protected override previousItem: IgxSlideComponent;
407
    private _interval: number;
408
    private _resourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN);
42✔
409
    private lastInterval: any;
410
    private playing: boolean;
411
    private destroyed: boolean;
412
    private destroy$ = new Subject<any>();
42✔
413
    private differ: IterableDiffer<IgxSlideComponent> | null = null;
42✔
414
    private incomingSlide: IgxSlideComponent;
415
    private _hasKeyboardFocusOnIndicators = false;
42✔
416

417
    /**
418
     * An accessor that sets the resource strings.
419
     * By default it uses EN resources.
420
     */
421
    @Input()
422
    public set resourceStrings(value: ICarouselResourceStrings) {
423
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
424
    }
425

426
    /**
427
     * An accessor that returns the resource strings.
428
     */
429
    public get resourceStrings(): ICarouselResourceStrings {
430
        return this._resourceStrings;
3,834✔
431
    }
432

433
    /** @hidden */
434
    public get getIndicatorTemplate(): TemplateRef<any> {
435
        if (this.indicatorTemplate) {
1,530✔
436
            return this.indicatorTemplate;
24✔
437
        }
438
        return this.defaultIndicator;
1,506✔
439
    }
440

441
    /** @hidden */
442
    public get getNextButtonTemplate(): TemplateRef<any> {
443
        if (this.nextButtonTemplate) {
386✔
444
            return this.nextButtonTemplate;
6✔
445
        }
446

447
        return this.defaultNextButton
380✔
448
    }
449

450
    /** @hidden */
451
    public get getPrevButtonTemplate(): TemplateRef<any> {
452
        if (this.prevButtonTemplate) {
386✔
453
            return this.prevButtonTemplate;
6✔
454
        }
455

456
        return this.defaultPrevButton
380✔
457
    }
458

459
    /** @hidden */
460
    public get indicatorsClass() {
461
        return {
388✔
462
            ['igx-carousel-indicators--focused']: this._hasKeyboardFocusOnIndicators,
463
            [`igx-carousel-indicators--${this.getIndicatorsClass()}`]: true
464
        };
465
    }
466

467
    /** @hidden */
468
    public get showIndicators(): boolean {
469
        return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
392✔
470
    }
471

472
    /** @hidden */
473
    public get showIndicatorsLabel(): boolean {
474
        return this.indicators && this.total > this.maximumIndicatorsCount;
786✔
475
    }
476

477
    /** @hidden */
478
    public get getCarouselLabel() {
479
        return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
2✔
480
    }
481

482
    /**
483
     * Returns the total number of `slides` in the carousel.
484
     * ```typescript
485
     * let slideCount =  this.carousel.total;
486
     * ```
487
     *
488
     * @memberOf IgxCarouselComponent
489
     */
490
    public get total(): number {
491
        return this.slides?.length;
10,777✔
492
    }
493

494
    /**
495
     * The index of the slide being currently shown.
496
     * ```typescript
497
     * let currentSlideNumber =  this.carousel.current;
498
     * ```
499
     *
500
     * @memberOf IgxCarouselComponent
501
     */
502
    public get current(): number {
503
        return !this.currentItem ? 0 : this.currentItem.index;
3,232✔
504
    }
505

506
    /**
507
     * Returns a boolean indicating if the carousel is playing.
508
     * ```typescript
509
     * let isPlaying =  this.carousel.isPlaying;
510
     * ```
511
     *
512
     * @memberOf IgxCarouselComponent
513
     */
514
    public get isPlaying(): boolean {
515
        return this.playing;
31✔
516
    }
517

518
    /**
519
     * Returns а boolean indicating if the carousel is destroyed.
520
     * ```typescript
521
     * let isDestroyed =  this.carousel.isDestroyed;
522
     * ```
523
     *
524
     * @memberOf IgxCarouselComponent
525
     */
526
    public get isDestroyed(): boolean {
527
        return this.destroyed;
1✔
528
    }
529
    /**
530
     * Returns a reference to the carousel element in the DOM.
531
     * ```typescript
532
     * let nativeElement =  this.carousel.nativeElement;
533
     * ```
534
     *
535
     * @memberof IgxCarouselComponent
536
     */
537
    public get nativeElement(): any {
538
        return this.element.nativeElement;
11✔
539
    }
540

541
    /**
542
     * Returns the time `interval` in milliseconds before the slide changes.
543
     * ```typescript
544
     * let timeInterval = this.carousel.interval;
545
     * ```
546
     *
547
     * @memberof IgxCarouselComponent
548
     */
549
    @Input()
550
    public get interval(): number {
551
        return this._interval;
3,632✔
552
    }
553

554
    /**
555
     * Sets the time `interval` in milliseconds before the slide changes.
556
     * If not set, the carousel will not change `slides` automatically.
557
     * ```html
558
     * <igx-carousel [interval]="1000"></igx-carousel>
559
     * ```
560
     *
561
     * @memberof IgxCarouselComponent
562
     */
563
    public set interval(value: number) {
564
        this._interval = +value;
28✔
565
        this.restartInterval();
28✔
566
    }
567

568
    constructor(
569
        cdr: ChangeDetectorRef,
570
        private element: ElementRef,
42✔
571
        private iterableDiffers: IterableDiffers,
42✔
572
        @Inject(IgxAngularAnimationService) animationService: AnimationService,
573
        private platformUtil: PlatformUtil,
42✔
574
        private dir: IgxDirectionality,
42✔
575
        @Inject(DOCUMENT) private document: any
42✔
576
    ) {
577
        super(animationService, cdr);
42✔
578
        this.differ = this.iterableDiffers.find([]).create(null);
42✔
579
    }
580

581
    /** @hidden */
582
    @HostListener('tap', ['$event'])
583
    public onTap(event) {
584
        // play pause only when tap on slide
585
        if (event.target && event.target.classList.contains('igx-slide')) {
4✔
586
            if (this.isPlaying) {
4✔
587
                if (this.pause) {
1✔
588
                    this.stoppedByInteraction = true;
1✔
589
                }
590
                this.stop();
1✔
591
            } else if (this.stoppedByInteraction) {
3✔
592
                this.play();
1✔
593
            }
594
        }
595
    }
596

597
    /** @hidden */
598
    @HostListener('mouseenter')
599
    public onMouseEnter() {
600
        if (this.pause && this.isPlaying) {
2✔
601
            this.stoppedByInteraction = true;
1✔
602
        }
603
        this.stop();
2✔
604
    }
605

606
    /** @hidden */
607
    @HostListener('mouseleave')
608
    public onMouseLeave() {
609
        if (this.stoppedByInteraction) {
2✔
610
            this.play();
1✔
611
        }
612
    }
613

614
    /** @hidden */
615
    @HostListener('panleft', ['$event'])
616
    public onPanLeft(event) {
617
        if (!this.vertical) {
7✔
618
            this.pan(event);
5✔
619
        }
620
    }
621

622
    /** @hidden */
623
    @HostListener('panright', ['$event'])
624
    public onPanRight(event) {
625
        if (!this.vertical) {
7✔
626
            this.pan(event);
5✔
627
        }
628
    }
629

630
    /** @hidden */
631
    @HostListener('panup', ['$event'])
632
    public onPanUp(event) {
633
        if (this.vertical) {
5✔
634
            this.pan(event);
3✔
635
        }
636
    }
637

638
    /** @hidden */
639
    @HostListener('pandown', ['$event'])
640
    public onPanDown(event) {
641
        if (this.vertical) {
5✔
642
            this.pan(event);
3✔
643
        }
644
    }
645

646
    /**
647
     * @hidden
648
     */
649
    @HostListener('panend', ['$event'])
650
    public onPanEnd(event) {
651
        if (!this.gesturesSupport) {
24✔
652
            return;
2✔
653
        }
654
        event.preventDefault();
22✔
655

656
        const slideSize = this.vertical
22✔
657
            ? this.currentItem.nativeElement.offsetHeight
658
            : this.currentItem.nativeElement.offsetWidth;
659
        const panOffset = (slideSize / 1000);
22✔
660
        const eventDelta = this.vertical ? event.deltaY : event.deltaX;
22✔
661
        const delta = Math.abs(eventDelta) + panOffset < slideSize ? Math.abs(eventDelta) : slideSize - panOffset;
22!
662
        const velocity = Math.abs(event.velocity);
22✔
663
        this.resetSlideStyles(this.currentItem);
22✔
664
        if (this.incomingSlide) {
22✔
665
            this.resetSlideStyles(this.incomingSlide);
12✔
666
            if (slideSize / 2 < delta || velocity > 1) {
12✔
667
                this.incomingSlide.direction = eventDelta < 0 ? Direction.NEXT : Direction.PREV;
8✔
668
                this.incomingSlide.previous = false;
8✔
669

670
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
8!
671
                    delta / slideSize : (slideSize - delta) / slideSize;
672

673
                if (velocity > 1) {
8✔
674
                    this.newDuration = this.defaultAnimationDuration / velocity;
4✔
675
                }
676
                this.incomingSlide.active = true;
8✔
677
            } else {
678
                this.currentItem.direction = eventDelta > 0 ? Direction.NEXT : Direction.PREV;
4✔
679
                this.previousItem = this.incomingSlide;
4✔
680
                this.previousItem.previous = true;
4✔
681
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
4!
682
                    Math.abs((slideSize - delta) / slideSize) : delta / slideSize;
683
                this.playAnimations();
4✔
684
            }
685
        }
686

687
        if (this.stoppedByInteraction) {
22!
688
            this.play();
×
689
        }
690
    }
691

692
    /** @hidden */
693
    public ngAfterContentInit() {
694
        this.slides.changes
42✔
695
            .pipe(takeUntil(this.destroy$))
696
            .subscribe((change: QueryList<IgxSlideComponent>) => this.initSlides(change));
13✔
697

698
        this.initSlides(this.slides);
42✔
699
    }
700

701
    /** @hidden */
702
    public ngOnDestroy() {
703
        this.destroy$.next(true);
43✔
704
        this.destroy$.complete();
43✔
705
        this.destroyed = true;
43✔
706
        if (this.lastInterval) {
43✔
707
            clearInterval(this.lastInterval);
24✔
708
        }
709
    }
710

711
    /** @hidden */
712
    public handleKeydownPrev(event: KeyboardEvent): void {
713
        if (this.platformUtil.isActivationKey(event)) {
2✔
714
            event.preventDefault();
2✔
715
            this.prev();
2✔
716
        }
717
    }
718

719
    /** @hidden */
720
    public handleKeydownNext(event: KeyboardEvent): void {
721
        if (this.platformUtil.isActivationKey(event)) {
2✔
722
            event.preventDefault();
2✔
723
            this.next();
2✔
724
        }
725
    }
726

727
    /** @hidden */
728
    public handleKeyUp(event: KeyboardEvent): void {
729
        if (event.key === this.platformUtil.KEYMAP.TAB) {
6✔
730
            this._hasKeyboardFocusOnIndicators = true;
6✔
731
        }
732
    }
733

734
    /** @hidden */
735
    public handleFocusOut(event: FocusEvent): void {
736
        const target = event.relatedTarget as HTMLElement;
14✔
737

738
        if (!target || !target.classList.contains('igx-carousel-indicators__indicator')) {
14✔
739
            this._hasKeyboardFocusOnIndicators = false;
1✔
740
        }
741
    }
742

743
    /** @hidden */
744
    public handleClick(): void {
745
        this._hasKeyboardFocusOnIndicators = false;
1✔
746
    }
747

748
    /** @hidden */
749
    public handleKeydown(event: KeyboardEvent): void {
750
        const { key } = event;
16✔
751
        const slides = this.slides.toArray();
16✔
752

753
        switch (key) {
16✔
754
            case this.platformUtil.KEYMAP.ARROW_LEFT:
755
                this.dir.rtl ? this.next() : this.prev();
4✔
756
                break;
4✔
757
            case this.platformUtil.KEYMAP.ARROW_RIGHT:
758
                this.dir.rtl ? this.prev() : this.next();
6✔
759
                break;
6✔
760
            case this.platformUtil.KEYMAP.HOME:
761
                event.preventDefault();
3✔
762
                this.select(this.dir.rtl ? last(slides) : first(slides));
3✔
763
                break;
3✔
764
            case this.platformUtil.KEYMAP.END:
765
                event.preventDefault();
3✔
766
                this.select(this.dir.rtl ? first(slides) : last(slides));
3✔
767
                break;
3✔
768
        }
769

770
        this.indicatorsElements[this.current].nativeElement.focus();
16✔
771
    }
772

773
    /**
774
     * Returns the slide corresponding to the provided `index` or null.
775
     * ```typescript
776
     * let slide1 =  this.carousel.get(1);
777
     * ```
778
     *
779
     * @memberOf IgxCarouselComponent
780
     */
781
    public get(index: number): IgxSlideComponent {
782
        return this.slides.find((slide) => slide.index === index);
6,792✔
783
    }
784

785
    /**
786
     * Adds a new slide to the carousel.
787
     * ```typescript
788
     * this.carousel.add(newSlide);
789
     * ```
790
     *
791
     * @memberOf IgxCarouselComponent
792
     */
793
    public add(slide: IgxSlideComponent) {
794
        const newSlides = this.slides.toArray();
3✔
795
        newSlides.push(slide);
3✔
796
        this.slides.reset(newSlides);
3✔
797
        this.slides.notifyOnChanges();
3✔
798
    }
799

800
    /**
801
     * Removes a slide from the carousel.
802
     * ```typescript
803
     * this.carousel.remove(slide);
804
     * ```
805
     *
806
     * @memberOf IgxCarouselComponent
807
     */
808
    public remove(slide: IgxSlideComponent) {
809
        if (slide && slide === this.get(slide.index)) { // check if the requested slide for delete is present in the carousel
4✔
810
            const newSlides = this.slides.toArray();
4✔
811
            newSlides.splice(slide.index, 1);
4✔
812
            this.slides.reset(newSlides);
4✔
813
            this.slides.notifyOnChanges();
4✔
814
        }
815
    }
816

817
    /**
818
     * Kicks in a transition for a given slide with a given `direction`.
819
     * ```typescript
820
     * this.carousel.select(this.carousel.get(2), Direction.NEXT);
821
     * ```
822
     *
823
     * @memberOf IgxCarouselComponent
824
     */
825
    public select(slide: IgxSlideComponent, direction: Direction = Direction.NONE) {
13✔
826
        if (slide && slide !== this.currentItem) {
2,969✔
827
            slide.direction = direction;
2,968✔
828
            slide.active = true;
2,968✔
829
        }
830
    }
831

832
    /**
833
     * Transitions to the next slide in the carousel.
834
     * ```typescript
835
     * this.carousel.next();
836
     * ```
837
     *
838
     * @memberOf IgxCarouselComponent
839
     */
840
    public next() {
841
        const index = this.getNextIndex();
2,945✔
842

843
        if (index === 0 && !this.loop) {
2,945✔
844
            this.stop();
1✔
845
            return;
1✔
846
        }
847
        return this.select(this.get(index), Direction.NEXT);
2,944✔
848
    }
849

850
    /**
851
     * Transitions to the previous slide in the carousel.
852
     * ```typescript
853
     * this.carousel.prev();
854
     * ```
855
     *
856
     * @memberOf IgxCarouselComponent
857
     */
858
    public prev() {
859
        const index = this.getPrevIndex();
13✔
860

861
        if (!this.loop && index === this.total - 1) {
13✔
862
            this.stop();
1✔
863
            return;
1✔
864
        }
865
        return this.select(this.get(index), Direction.PREV);
12✔
866
    }
867

868
    /**
869
     * Resumes playing of the carousel if in paused state.
870
     * No operation otherwise.
871
     * ```typescript
872
     * this.carousel.play();
873
     * }
874
     * ```
875
     *
876
     * @memberOf IgxCarouselComponent
877
     */
878
    public play() {
879
        if (!this.playing) {
60✔
880
            this.playing = true;
46✔
881
            this.carouselPlaying.emit(this);
46✔
882
            this.restartInterval();
46✔
883
            this.stoppedByInteraction = false;
46✔
884
        }
885
    }
886

887
    /**
888
     * Stops slide transitions if the `pause` option is set to `true`.
889
     * No operation otherwise.
890
     * ```typescript
891
     *  this.carousel.stop();
892
     * }
893
     * ```
894
     *
895
     * @memberOf IgxCarouselComponent
896
     */
897
    public stop() {
898
        if (this.pause) {
9✔
899
            this.playing = false;
9✔
900
            this.carouselPaused.emit(this);
9✔
901
            this.resetInterval();
9✔
902
        }
903
    }
904

905
    protected getPreviousElement(): HTMLElement {
906
        return this.previousItem.nativeElement;
2✔
907
    }
908

909
    protected getCurrentElement(): HTMLElement {
910
        return this.currentItem.nativeElement;
3✔
911
    }
912

913
    private resetInterval() {
914
        if (this.lastInterval) {
134✔
915
            clearInterval(this.lastInterval);
67✔
916
            this.lastInterval = null;
67✔
917
        }
918
    }
919

920
    private restartInterval() {
921
        this.resetInterval();
125✔
922

923
        if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
125✔
924
            this.lastInterval = setInterval(() => {
93✔
925
                const tick = +this.interval;
2,927✔
926
                if (this.playing && this.total && !isNaN(tick) && tick > 0) {
2,927!
927
                    this.next();
2,927✔
928
                } else {
929
                    this.stop();
×
930
                }
931
            }, this.interval);
932
        }
933
    }
934

935
    /** @hidden */
936
    public get nextButtonDisabled() {
937
        return !this.loop && this.current === (this.total - 1);
772✔
938
    }
939

940
    /** @hidden */
941
    public get prevButtonDisabled() {
942
        return !this.loop && this.current === 0;
772✔
943
    }
944

945
    private get indicatorsElements() {
946
        return this._indicators.toArray();
16✔
947
    }
948

949
    private focusElement() {
UNCOV
950
        const focusedElement = this.document.activeElement;
×
951

UNCOV
952
        if (focusedElement.classList.contains('igx-carousel-indicators__indicator')) {
×
953
            this.indicatorsElements[this.current].nativeElement.focus();
×
954
        } else {
UNCOV
955
            this.focusSlideElement();
×
956
        }
957
    }
958

959
    private getIndicatorsClass(): string {
960
        switch (this.indicatorsOrientation) {
388!
961
            case CarouselIndicatorsOrientation.top:
962
                return CarouselIndicatorsOrientation.start;
×
963
            case CarouselIndicatorsOrientation.bottom:
964
                return CarouselIndicatorsOrientation.end;
×
965
            default:
966
                return this.indicatorsOrientation;
388✔
967
        }
968
    }
969

970
    private getNextIndex(): number {
971
        return (this.current + 1) % this.total;
2,953✔
972
    }
973

974
    private getPrevIndex(): number {
975
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
21✔
976
    }
977

978
    private resetSlideStyles(slide: IgxSlideComponent) {
979
        slide.nativeElement.style.transform = '';
38✔
980
        slide.nativeElement.style.opacity = '';
38✔
981
    }
982

983
    private pan(event) {
984
        const slideSize = this.vertical
16✔
985
            ? this.currentItem.nativeElement.offsetHeight
986
            : this.currentItem.nativeElement.offsetWidth;
987
        const panOffset = (slideSize / 1000);
16✔
988
        const delta = this.vertical ? event.deltaY : event.deltaX;
16✔
989
        const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
16✔
990
        const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
16✔
991

992
        if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
16✔
993
            return;
2✔
994
        }
995

996
        if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
14✔
997
            this.incomingSlide = null;
2✔
998
            return;
2✔
999
        }
1000

1001
        event.preventDefault();
12✔
1002
        if (this.isPlaying) {
12!
1003
            this.stoppedByInteraction = true;
×
1004
            this.stop();
×
1005
        }
1006

1007
        if (this.previousItem && this.previousItem.previous) {
12✔
1008
            this.previousItem.previous = false;
4✔
1009
        }
1010
        this.finishAnimations();
12✔
1011

1012
        if (this.incomingSlide) {
12✔
1013
            if (index !== this.incomingSlide.index) {
8✔
1014
                this.resetSlideStyles(this.incomingSlide);
4✔
1015
                this.incomingSlide.previous = false;
4✔
1016
                this.incomingSlide = this.get(index);
4✔
1017
            }
1018
        } else {
1019
            this.incomingSlide = this.get(index);
4✔
1020
        }
1021
        this.incomingSlide.previous = true;
12✔
1022

1023
        if (this.animationType === CarouselAnimationType.fade) {
12!
1024
            this.currentItem.nativeElement.style.opacity = `${Math.abs(offset) / slideSize}`;
×
1025
        } else {
1026
            this.currentItem.nativeElement.style.transform = this.vertical
12✔
1027
                ? `translateY(${delta}px)`
1028
                : `translateX(${delta}px)`;
1029
            this.incomingSlide.nativeElement.style.transform = this.vertical
12✔
1030
                ? `translateY(${offset}px)`
1031
                : `translateX(${offset}px)`;
1032
        }
1033
    }
1034

1035
    private unsubscriber(slide: IgxSlideComponent) {
1036
        return merge(this.destroy$, slide.isDestroyed);
175✔
1037
    }
1038

1039
    private onSlideActivated(slide: IgxSlideComponent) {
1040
        if (slide.active && slide !== this.currentItem) {
100✔
1041
            if (slide.direction === Direction.NONE) {
51✔
1042
                const newIndex = slide.index;
12✔
1043
                slide.direction = newIndex > this.current ? Direction.NEXT : Direction.PREV;
12✔
1044
            }
1045

1046
            if (this.currentItem) {
51✔
1047
                if (this.previousItem && this.previousItem.previous) {
40!
1048
                    this.previousItem.previous = false;
×
1049
                }
1050
                this.currentItem.direction = slide.direction;
40✔
1051
                this.currentItem.active = false;
40✔
1052

1053
                this.previousItem = this.currentItem;
40✔
1054
                this.currentItem = slide;
40✔
1055
                this.triggerAnimations();
40✔
1056
            } else {
1057
                this.currentItem = slide;
11✔
1058
            }
1059
            this.slideChanged.emit({ carousel: this, slide });
51✔
1060
            this.restartInterval();
51✔
1061
        }
1062
    }
1063

1064

1065
    private finishAnimations() {
1066
        if (this.animationStarted(this.leaveAnimationPlayer)) {
12!
1067
            this.leaveAnimationPlayer.finish();
×
1068
        }
1069

1070
        if (this.animationStarted(this.enterAnimationPlayer)) {
12!
1071
            this.enterAnimationPlayer.finish();
×
1072
        }
1073
    }
1074

1075
    private initSlides(change: QueryList<IgxSlideComponent>) {
1076
        const diff = this.differ.diff(change.toArray());
55✔
1077
        if (diff) {
55✔
1078
            this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
210✔
1079
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
55✔
1080
                const slide = record.item;
175✔
1081
                slide.total = this.total;
175✔
1082
                this.slideAdded.emit({ carousel: this, slide });
175✔
1083
                if (slide.active) {
175✔
1084
                    this.currentItem = slide;
17✔
1085
                }
1086
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
175✔
1087
            });
1088

1089
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
55✔
1090
                const slide = record.item;
10✔
1091
                this.slideRemoved.emit({ carousel: this, slide });
10✔
1092
                if (slide.active) {
10✔
1093
                    slide.active = false;
3✔
1094
                    this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
3✔
1095
                }
1096
            });
1097

1098
            this.updateSlidesSelection();
55✔
1099
        }
1100
    }
1101

1102
    private updateSlidesSelection() {
1103
        if (this.platformUtil.isBrowser) {
55✔
1104
            requestAnimationFrame(() => {
55✔
1105
                if (this.currentItem) {
55✔
1106
                    this.currentItem.active = true;
31✔
1107
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
122✔
1108
                    activeSlides.forEach(slide => slide.active = false);
31✔
1109
                } else if (this.total) {
24✔
1110
                    this.slides.first.active = true;
23✔
1111
                }
1112
                this.play();
55✔
1113
            });
1114
        }
1115
    }
1116
    private focusSlideElement() {
UNCOV
1117
        if (this.leaveAnimationPlayer) {
×
1118
            this.leaveAnimationPlayer.animationEnd
×
1119
                .pipe(takeUntil(this.destroy$))
1120
                .subscribe(() => {
1121
                    this.slides.find(s => s.active).nativeElement.focus();
×
1122
                });
1123
        } else {
UNCOV
1124
            requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
×
1125
        }
1126
    }
1127

1128
}
1129

1130
export interface ISlideEventArgs extends IBaseEventArgs {
1131
    carousel: IgxCarouselComponent;
1132
    slide: IgxSlideComponent;
1133
}
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

© 2026 Coveralls, Inc