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

IgniteUI / igniteui-angular / 19701971113

26 Nov 2025 11:20AM UTC coverage: 91.609% (+0.001%) from 91.608%
19701971113

Pull #16512

github

web-flow
Merge f41f1ea60 into 382635a60
Pull Request #16512: refactor(button): refactor button themes

13913 of 16328 branches covered (85.21%)

28057 of 30627 relevant lines covered (91.61%)

34370.59 hits per line

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

95.6
/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts
1
import { NgClass, NgTemplateOutlet } 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
    DOCUMENT
26
} from '@angular/core';
27
import { HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
28
import { merge, Subject } from 'rxjs';
29
import { takeUntil } from 'rxjs/operators';
30
import { CarouselResourceStringsEN, ICarouselResourceStrings, ɵIgxDirectionality } from 'igniteui-angular/core';
31
import { first, IBaseEventArgs, last, PlatformUtil } from 'igniteui-angular/core';
32
import { IgxAngularAnimationService } from 'igniteui-angular/core';
33
import { AnimationService } from 'igniteui-angular/core';
34
import { CarouselAnimationDirection, IgxCarouselComponentBase } from './carousel-base';
35
import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives';
36
import { IgxSlideComponent } from './slide.component';
37
import { IgxIconComponent } from 'igniteui-angular/icon';
38
import { IgxButtonDirective } from 'igniteui-angular/directives';
39
import { getCurrentResourceStrings } from 'igniteui-angular/core';
40
import { HammerGesturesManager } from 'igniteui-angular/core';
41
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
42

43
let NEXT_ID = 0;
3✔
44

45

46
@Injectable()
47
export class CarouselHammerConfig extends HammerGestureConfig {
3✔
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, NgClass, NgTemplateOutlet]
88
})
89
export class IgxCarouselComponent extends IgxCarouselComponentBase implements OnDestroy, AfterContentInit {
3✔
90

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

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

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

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

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

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

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

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

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

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

195

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

446
        return this.defaultNextButton
378✔
447
    }
448

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

455
        return this.defaultPrevButton
378✔
456
    }
457

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

697
        this.initSlides(this.slides);
43✔
698
    }
699

700
    /** @hidden */
701
    public override ngOnDestroy() {
702
        super.ngOnDestroy();
45✔
703
        this.destroy$.next(true);
45✔
704
        this.destroy$.complete();
45✔
705
        this.destroyed = true;
45✔
706
        if (this.lastInterval) {
45✔
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);
4,698✔
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
     * Switches to the passed-in slide with a given `direction`.
819
     * ```typescript
820
     * const slide = this.carousel.get(2);
821
     * this.carousel.select(slide, CarouselAnimationDirection.NEXT);
822
     * ```
823
     *
824
     * @memberOf IgxCarouselComponent
825
     */
826
    public select(slide: IgxSlideComponent, direction?: CarouselAnimationDirection): void;
827
    /**
828
     * Switches to slide by index with a given `direction`.
829
     * ```typescript
830
     * this.carousel.select(2, CarouselAnimationDirection.NEXT);
831
     * ```
832
     *
833
     * @memberOf IgxCarouselComponent
834
     */
835
    public select(index: number, direction?: CarouselAnimationDirection): void;
836
    public select(slideOrIndex: IgxSlideComponent | number, direction: CarouselAnimationDirection = CarouselAnimationDirection.NONE): void {
16✔
837
        const slide = typeof slideOrIndex === 'number'
1,990✔
838
            ? this.get(slideOrIndex)
839
            : slideOrIndex;
840

841
        if (slide && slide !== this.currentItem) {
1,990✔
842
            slide.direction = direction;
1,987✔
843
            slide.active = true;
1,987✔
844
        }
845
    }
846

847
    /**
848
     * Transitions to the next slide in the carousel.
849
     * ```typescript
850
     * this.carousel.next();
851
     * ```
852
     *
853
     * @memberOf IgxCarouselComponent
854
     */
855
    public next() {
856
        const index = this.getNextIndex();
1,963✔
857

858
        if (index === 0 && !this.loop) {
1,963✔
859
            this.stop();
1✔
860
            return;
1✔
861
        }
862
        return this.select(this.get(index), CarouselAnimationDirection.NEXT);
1,962✔
863
    }
864

865
    /**
866
     * Transitions to the previous slide in the carousel.
867
     * ```typescript
868
     * this.carousel.prev();
869
     * ```
870
     *
871
     * @memberOf IgxCarouselComponent
872
     */
873
    public prev() {
874
        const index = this.getPrevIndex();
13✔
875

876
        if (!this.loop && index === this.total - 1) {
13✔
877
            this.stop();
1✔
878
            return;
1✔
879
        }
880
        return this.select(this.get(index), CarouselAnimationDirection.PREV);
12✔
881
    }
882

883
    /**
884
     * Resumes playing of the carousel if in paused state.
885
     * No operation otherwise.
886
     * ```typescript
887
     * this.carousel.play();
888
     * }
889
     * ```
890
     *
891
     * @memberOf IgxCarouselComponent
892
     */
893
    public play() {
894
        if (!this.playing) {
61✔
895
            this.playing = true;
47✔
896
            this.carouselPlaying.emit(this);
47✔
897
            this.restartInterval();
47✔
898
            this.stoppedByInteraction = false;
47✔
899
        }
900
    }
901

902
    /**
903
     * Stops slide transitions if the `pause` option is set to `true`.
904
     * No operation otherwise.
905
     * ```typescript
906
     *  this.carousel.stop();
907
     * }
908
     * ```
909
     *
910
     * @memberOf IgxCarouselComponent
911
     */
912
    public stop() {
913
        if (this.pause) {
9✔
914
            this.playing = false;
9✔
915
            this.carouselPaused.emit(this);
9✔
916
            this.resetInterval();
9✔
917
        }
918
    }
919

920
    protected getPreviousElement(): HTMLElement {
921
        return this.previousItem.nativeElement;
3✔
922
    }
923

924
    protected getCurrentElement(): HTMLElement {
925
        return this.currentItem.nativeElement;
4✔
926
    }
927

928
    private resetInterval() {
929
        if (this.lastInterval) {
137✔
930
            clearInterval(this.lastInterval);
68✔
931
            this.lastInterval = null;
68✔
932
        }
933
    }
934

935
    private restartInterval() {
936
        this.resetInterval();
128✔
937

938
        if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
128✔
939
            this.lastInterval = setInterval(() => {
94✔
940
                const tick = +this.interval;
1,944✔
941
                if (this.playing && this.total && !isNaN(tick) && tick > 0) {
1,944!
942
                    this.next();
1,944✔
943
                } else {
944
                    this.stop();
×
945
                }
946
            }, this.interval);
947
        }
948
    }
949

950
    /** @hidden */
951
    public get nextButtonDisabled() {
952
        return !this.loop && this.current === (this.total - 1);
768✔
953
    }
954

955
    /** @hidden */
956
    public get prevButtonDisabled() {
957
        return !this.loop && this.current === 0;
768✔
958
    }
959

960
    private get indicatorsElements() {
961
        return this._indicators.toArray();
16✔
962
    }
963

964
    private getIndicatorsClass(): string {
965
        switch (this.indicatorsOrientation) {
386!
966
            case CarouselIndicatorsOrientation.top:
967
                return CarouselIndicatorsOrientation.start;
×
968
            case CarouselIndicatorsOrientation.bottom:
969
                return CarouselIndicatorsOrientation.end;
×
970
            default:
971
                return this.indicatorsOrientation;
386✔
972
        }
973
    }
974

975
    private getNextIndex(): number {
976
        return (this.current + 1) % this.total;
1,971✔
977
    }
978

979
    private getPrevIndex(): number {
980
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
21✔
981
    }
982

983
    private resetSlideStyles(slide: IgxSlideComponent) {
984
        slide.nativeElement.style.transform = '';
38✔
985
        slide.nativeElement.style.opacity = '';
38✔
986
    }
987

988
    private pan(event) {
989
        const slideSize = this.vertical
16✔
990
            ? this.currentItem.nativeElement.offsetHeight
991
            : this.currentItem.nativeElement.offsetWidth;
992
        const panOffset = (slideSize / 1000);
16✔
993
        const delta = this.vertical ? event.deltaY : event.deltaX;
16✔
994
        const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
16✔
995
        const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
16✔
996

997
        if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
16✔
998
            return;
2✔
999
        }
1000

1001
        if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
14✔
1002
            this.incomingSlide = null;
2✔
1003
            return;
2✔
1004
        }
1005

1006
        event.preventDefault();
12✔
1007
        if (this.isPlaying) {
12!
1008
            this.stoppedByInteraction = true;
×
1009
            this.stop();
×
1010
        }
1011

1012
        if (this.previousItem && this.previousItem.previous) {
12✔
1013
            this.previousItem.previous = false;
4✔
1014
        }
1015
        this.finishAnimations();
12✔
1016

1017
        if (this.incomingSlide) {
12✔
1018
            if (index !== this.incomingSlide.index) {
8✔
1019
                this.resetSlideStyles(this.incomingSlide);
4✔
1020
                this.incomingSlide.previous = false;
4✔
1021
                this.incomingSlide = this.get(index);
4✔
1022
            }
1023
        } else {
1024
            this.incomingSlide = this.get(index);
4✔
1025
        }
1026
        this.incomingSlide.previous = true;
12✔
1027

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

1040
    private unsubscriber(slide: IgxSlideComponent) {
1041
        return merge(this.destroy$, slide.isDestroyed);
179✔
1042
    }
1043

1044
    private onSlideActivated(slide: IgxSlideComponent) {
1045
        if (slide.active && slide !== this.currentItem) {
103✔
1046
            if (slide.direction === CarouselAnimationDirection.NONE) {
53✔
1047
                const newIndex = slide.index;
13✔
1048
                slide.direction = newIndex > this.current ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
13✔
1049
            }
1050

1051
            if (this.currentItem) {
53✔
1052
                if (this.previousItem && this.previousItem.previous) {
42!
1053
                    this.previousItem.previous = false;
×
1054
                }
1055
                this.currentItem.direction = slide.direction;
42✔
1056
                this.currentItem.active = false;
42✔
1057

1058
                this.previousItem = this.currentItem;
42✔
1059
                this.currentItem = slide;
42✔
1060
                this.triggerAnimations();
42✔
1061
            } else {
1062
                this.currentItem = slide;
11✔
1063
            }
1064
            this.slideChanged.emit({ carousel: this, slide });
53✔
1065
            this.restartInterval();
53✔
1066
            this.cdr.markForCheck();
53✔
1067
        }
1068
    }
1069

1070

1071
    private finishAnimations() {
1072
        if (this.animationStarted(this.leaveAnimationPlayer)) {
12!
1073
            this.leaveAnimationPlayer.finish();
×
1074
        }
1075

1076
        if (this.animationStarted(this.enterAnimationPlayer)) {
12!
1077
            this.enterAnimationPlayer.finish();
×
1078
        }
1079
    }
1080

1081
    private initSlides(change: QueryList<IgxSlideComponent>) {
1082
        const diff = this.differ.diff(change.toArray());
56✔
1083
        if (diff) {
56✔
1084
            this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
214✔
1085
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
56✔
1086
                const slide = record.item;
179✔
1087
                slide.total = this.total;
179✔
1088
                this.slideAdded.emit({ carousel: this, slide });
179✔
1089
                if (slide.active) {
179✔
1090
                    this.currentItem = slide;
17✔
1091
                }
1092
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
179✔
1093
            });
1094

1095
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
56✔
1096
                const slide = record.item;
10✔
1097
                this.slideRemoved.emit({ carousel: this, slide });
10✔
1098
                if (slide.active) {
10✔
1099
                    slide.active = false;
3✔
1100
                    this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
3✔
1101
                }
1102
            });
1103

1104
            this.updateSlidesSelection();
56✔
1105
        }
1106
    }
1107

1108
    private updateSlidesSelection() {
1109
        if (this.platformUtil.isBrowser) {
56✔
1110
            requestAnimationFrame(() => {
56✔
1111
                if (this.currentItem) {
56✔
1112
                    this.currentItem.active = true;
32✔
1113
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
126✔
1114
                    activeSlides.forEach(slide => slide.active = false);
32✔
1115
                } else if (this.total) {
24✔
1116
                    this.slides.first.active = true;
23✔
1117
                }
1118
                this.play();
56✔
1119
            });
1120
        }
1121
    }
1122
}
1123

1124
export interface ISlideEventArgs extends IBaseEventArgs {
1125
    carousel: IgxCarouselComponent;
1126
    slide: IgxSlideComponent;
1127
}
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