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

IgniteUI / igniteui-angular / 14102175764

27 Mar 2025 08:34AM UTC coverage: 91.59% (-0.003%) from 91.593%
14102175764

push

github

web-flow
[Grid]: fix sorting icon color (#15572)

12992 of 15227 branches covered (85.32%)

26321 of 28738 relevant lines covered (91.59%)

34035.23 hits per line

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

95.24
/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, ICarouselComponentBase, IGX_CAROUSEL_COMPONENT, 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
        { provide: IGX_CAROUSEL_COMPONENT, useExisting: IgxCarouselComponent }
80
    ],
81
    selector: 'igx-carousel',
82
    templateUrl: 'carousel.component.html',
83
    styles: [`
84
    :host {
85
        display: block;
86
        outline-style: none;
87
    }`],
88
    standalone: true,
89
    imports: [IgxButtonDirective, IgxIconComponent, NgIf, NgClass, NgFor, NgTemplateOutlet]
90
})
91

92
export class IgxCarouselComponent extends IgxCarouselComponentBase implements ICarouselComponentBase, OnDestroy, AfterContentInit {
2✔
93

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

116
    /** @hidden */
117
    @HostBinding('attr.aria-roledescription')
118
    public roleDescription = 'carousel';
43✔
119

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

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

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

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

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

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

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

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

198

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

210
    /**
211
     * Controls whether the carousel should support keyboard navigation.
212
     * Default value is `false`.
213
     * ```html
214
     * <igx-carousel [keyboardSupport]="true"></igx-carousel>
215
     * ```
216
     *
217
     * @memberOf IgxCarouselComponent
218
     * @deprecated in version 18.2.0.
219
     */
220
    @Input({ transform: booleanAttribute }) public keyboardSupport = false;
43✔
221

222
    /**
223
     * Controls whether the carousel should support gestures.
224
     * Default value is `true`.
225
     * ```html
226
     * <igx-carousel [gesturesSupport]="false"></igx-carousel>
227
     * ```
228
     *
229
     * @memberOf IgxCarouselComponent
230
     */
231
    @Input({ transform: booleanAttribute }) public gesturesSupport = true;
43✔
232

233
    /**
234
     * Controls the maximum indexes that can be shown.
235
     * Default value is `5`.
236
     * ```html
237
     * <igx-carousel [maximumIndicatorsCount]="10"></igx-carousel>
238
     * ```
239
     *
240
     * @memberOf IgxCarouselComponent
241
     */
242
    @Input() public maximumIndicatorsCount = 5;
43✔
243

244
    /**
245
     * Gets/sets the display mode of carousel indicators. It can be top or bottom.
246
     * Default value is `bottom`.
247
     * ```html
248
     * <igx-carousel indicatorsOrientation='top'>
249
     * <igx-carousel>
250
     * ```
251
     *
252
     * @memberOf IgxSlideComponent
253
     */
254
    @Input() public indicatorsOrientation: CarouselIndicatorsOrientation = CarouselIndicatorsOrientation.bottom;
43✔
255

256
    /**
257
     * Gets/sets the animation type of carousel.
258
     * Default value is `slide`.
259
     * ```html
260
     * <igx-carousel animationType='none'>
261
     * <igx-carousel>
262
     * ```
263
     *
264
     * @memberOf IgxSlideComponent
265
     */
266
    @Input() public override animationType: CarouselAnimationType = CarouselAnimationType.slide;
43✔
267

268
    /**
269
     * The custom template, if any, that should be used when rendering carousel indicators
270
     *
271
     * ```typescript
272
     * // Set in typescript
273
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
274
     * myComponent.carousel.indicatorTemplate = myCustomTemplate;
275
     * ```
276
     * ```html
277
     * <!-- Set in markup -->
278
     *  <igx-carousel #carousel>
279
     *      ...
280
     *      <ng-template igxCarouselIndicator let-slide>
281
     *         <igx-icon *ngIf="slide.active">brightness_7</igx-icon>
282
     *         <igx-icon *ngIf="!slide.active">brightness_5</igx-icon>
283
     *      </ng-template>
284
     *  </igx-carousel>
285
     * ```
286
     */
287
    @ContentChild(IgxCarouselIndicatorDirective, { read: TemplateRef, static: false })
288
    public indicatorTemplate: TemplateRef<any> = null;
43✔
289

290
    /**
291
     * The custom template, if any, that should be used when rendering carousel next button
292
     *
293
     * ```typescript
294
     * // Set in typescript
295
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
296
     * myComponent.carousel.nextButtonTemplate = myCustomTemplate;
297
     * ```
298
     * ```html
299
     * <!-- Set in markup -->
300
     *  <igx-carousel #carousel>
301
     *      ...
302
     *      <ng-template igxCarouselNextButton let-disabled>
303
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
304
     *              <igx-icon name="add"></igx-icon>
305
     *          </button>
306
     *      </ng-template>
307
     *  </igx-carousel>
308
     * ```
309
     */
310
    @ContentChild(IgxCarouselNextButtonDirective, { read: TemplateRef, static: false })
311
    public nextButtonTemplate: TemplateRef<any> = null;
43✔
312

313
    /**
314
     * The custom template, if any, that should be used when rendering carousel previous button
315
     *
316
     * ```typescript
317
     * // Set in typescript
318
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
319
     * myComponent.carousel.prevButtonTemplate = myCustomTemplate;
320
     * ```
321
     * ```html
322
     * <!-- Set in markup -->
323
     *  <igx-carousel #carousel>
324
     *      ...
325
     *      <ng-template igxCarouselPrevButton let-disabled>
326
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
327
     *              <igx-icon name="remove"></igx-icon>
328
     *          </button>
329
     *      </ng-template>
330
     *  </igx-carousel>
331
     * ```
332
     */
333
    @ContentChild(IgxCarouselPrevButtonDirective, { read: TemplateRef, static: false })
334
    public prevButtonTemplate: TemplateRef<any> = null;
43✔
335

336
    /**
337
     * The collection of `slides` currently in the carousel.
338
     * ```typescript
339
     * let slides: QueryList<IgxSlideComponent> = this.carousel.slides;
340
     * ```
341
     *
342
     * @memberOf IgxCarouselComponent
343
     */
344
    @ContentChildren(IgxSlideComponent)
345
    public slides: QueryList<IgxSlideComponent>;
346

347
    /**
348
     * An event that is emitted after a slide transition has happened.
349
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
350
     * ```html
351
     * <igx-carousel (slideChanged)="slideChanged($event)"></igx-carousel>
352
     * ```
353
     *
354
     * @memberOf IgxCarouselComponent
355
     */
356
    @Output() public slideChanged = new EventEmitter<ISlideEventArgs>();
43✔
357

358
    /**
359
     * An event that is emitted after a slide has been added to the carousel.
360
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
361
     * ```html
362
     * <igx-carousel (slideAdded)="slideAdded($event)"></igx-carousel>
363
     * ```
364
     *
365
     * @memberOf IgxCarouselComponent
366
     */
367
    @Output() public slideAdded = new EventEmitter<ISlideEventArgs>();
43✔
368

369
    /**
370
     * An event that is emitted after a slide has been removed from the carousel.
371
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
372
     * ```html
373
     * <igx-carousel (slideRemoved)="slideRemoved($event)"></igx-carousel>
374
     * ```
375
     *
376
     * @memberOf IgxCarouselComponent
377
     */
378
    @Output() public slideRemoved = new EventEmitter<ISlideEventArgs>();
43✔
379

380
    /**
381
     * An event that is emitted after the carousel has been paused.
382
     * Provides a reference to the `IgxCarouselComponent` as an event argument.
383
     * ```html
384
     * <igx-carousel (carouselPaused)="carouselPaused($event)"></igx-carousel>
385
     * ```
386
     *
387
     * @memberOf IgxCarouselComponent
388
     */
389
    @Output() public carouselPaused = new EventEmitter<IgxCarouselComponent>();
43✔
390

391
    /**
392
     * An event that is emitted after the carousel has resumed transitioning between `slides`.
393
     * Provides a reference to the `IgxCarouselComponent` as an event argument.
394
     * ```html
395
     * <igx-carousel (carouselPlaying)="carouselPlaying($event)"></igx-carousel>
396
     * ```
397
     *
398
     * @memberOf IgxCarouselComponent
399
     */
400
    @Output() public carouselPlaying = new EventEmitter<IgxCarouselComponent>();
43✔
401

402
    @ViewChild('defaultIndicator', { read: TemplateRef, static: true })
403
    private defaultIndicator: TemplateRef<any>;
404

405
    @ViewChild('defaultNextButton', { read: TemplateRef, static: true })
406
    private defaultNextButton: TemplateRef<any>;
407

408
    @ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
409
    private defaultPrevButton: TemplateRef<any>;
410

411
    @ViewChildren('indicators', { read: ElementRef })
412
    private _indicators: QueryList<ElementRef<HTMLDivElement>>;
413

414
    /**
415
     * @hidden
416
     * @internal
417
     */
418
    public stoppedByInteraction: boolean;
419
    protected override currentItem: IgxSlideComponent;
420
    protected override previousItem: IgxSlideComponent;
421
    private _interval: number;
422
    private _resourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN);
43✔
423
    private lastInterval: any;
424
    private playing: boolean;
425
    private destroyed: boolean;
426
    private destroy$ = new Subject<any>();
43✔
427
    private differ: IterableDiffer<IgxSlideComponent> | null = null;
43✔
428
    private incomingSlide: IgxSlideComponent;
429
    private _hasKeyboardFocusOnIndicators = false;
43✔
430

431
    /**
432
     * An accessor that sets the resource strings.
433
     * By default it uses EN resources.
434
     */
435
    @Input()
436
    public set resourceStrings(value: ICarouselResourceStrings) {
437
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
438
    }
439

440
    /**
441
     * An accessor that returns the resource strings.
442
     */
443
    public get resourceStrings(): ICarouselResourceStrings {
444
        return this._resourceStrings;
3,852✔
445
    }
446

447
    /** @hidden */
448
    public get getIndicatorTemplate(): TemplateRef<any> {
449
        if (this.indicatorTemplate) {
1,534✔
450
            return this.indicatorTemplate;
24✔
451
        }
452
        return this.defaultIndicator;
1,510✔
453
    }
454

455
    /** @hidden */
456
    public get getNextButtonTemplate(): TemplateRef<any> {
457
        if (this.nextButtonTemplate) {
390✔
458
            return this.nextButtonTemplate;
6✔
459
        }
460

461
        return this.defaultNextButton
384✔
462
    }
463

464
    /** @hidden */
465
    public get getPrevButtonTemplate(): TemplateRef<any> {
466
        if (this.prevButtonTemplate) {
390✔
467
            return this.prevButtonTemplate;
6✔
468
        }
469

470
        return this.defaultPrevButton
384✔
471
    }
472

473
    /** @hidden */
474
    public get indicatorsClass() {
475
        return {
392✔
476
            ['igx-carousel-indicators--focused']: this._hasKeyboardFocusOnIndicators,
477
            [`igx-carousel-indicators--${this.indicatorsOrientation}`]: true
478
        };
479
    }
480

481
    /** @hidden */
482
    public get showIndicators(): boolean {
483
        return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
396✔
484
    }
485

486
    /** @hidden */
487
    public get showIndicatorsLabel(): boolean {
488
        return this.indicators && this.total > this.maximumIndicatorsCount;
796✔
489
    }
490

491
    /** @hidden */
492
    public get getCarouselLabel() {
493
        return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
4✔
494
    }
495

496
    /**
497
     * Returns the total number of `slides` in the carousel.
498
     * ```typescript
499
     * let slideCount =  this.carousel.total;
500
     * ```
501
     *
502
     * @memberOf IgxCarouselComponent
503
     */
504
    public get total(): number {
505
        return this.slides?.length;
11,314✔
506
    }
507

508
    /**
509
     * The index of the slide being currently shown.
510
     * ```typescript
511
     * let currentSlideNumber =  this.carousel.current;
512
     * ```
513
     *
514
     * @memberOf IgxCarouselComponent
515
     */
516
    public get current(): number {
517
        return !this.currentItem ? 0 : this.currentItem.index;
3,484✔
518
    }
519

520
    /**
521
     * Returns a boolean indicating if the carousel is playing.
522
     * ```typescript
523
     * let isPlaying =  this.carousel.isPlaying;
524
     * ```
525
     *
526
     * @memberOf IgxCarouselComponent
527
     */
528
    public get isPlaying(): boolean {
529
        return this.playing;
31✔
530
    }
531

532
    /**
533
     * Returns а boolean indicating if the carousel is destroyed.
534
     * ```typescript
535
     * let isDestroyed =  this.carousel.isDestroyed;
536
     * ```
537
     *
538
     * @memberOf IgxCarouselComponent
539
     */
540
    public get isDestroyed(): boolean {
541
        return this.destroyed;
1✔
542
    }
543
    /**
544
     * Returns a reference to the carousel element in the DOM.
545
     * ```typescript
546
     * let nativeElement =  this.carousel.nativeElement;
547
     * ```
548
     *
549
     * @memberof IgxCarouselComponent
550
     */
551
    public get nativeElement(): any {
552
        return this.element.nativeElement;
24✔
553
    }
554

555
    /**
556
     * Returns the time `interval` in milliseconds before the slide changes.
557
     * ```typescript
558
     * let timeInterval = this.carousel.interval;
559
     * ```
560
     *
561
     * @memberof IgxCarouselComponent
562
     */
563
    @Input()
564
    public get interval(): number {
565
        return this._interval;
3,901✔
566
    }
567

568
    /**
569
     * Sets the time `interval` in milliseconds before the slide changes.
570
     * If not set, the carousel will not change `slides` automatically.
571
     * ```html
572
     * <igx-carousel [interval]="1000"></igx-carousel>
573
     * ```
574
     *
575
     * @memberof IgxCarouselComponent
576
     */
577
    public set interval(value: number) {
578
        this._interval = +value;
29✔
579
        this.restartInterval();
29✔
580
    }
581

582
    constructor(
583
        cdr: ChangeDetectorRef,
584
        private element: ElementRef,
43✔
585
        private iterableDiffers: IterableDiffers,
43✔
586
        @Inject(IgxAngularAnimationService) animationService: AnimationService,
587
        private platformUtil: PlatformUtil,
43✔
588
        private dir: IgxDirectionality,
43✔
589
        @Inject(DOCUMENT) private document: any
43✔
590
    ) {
591
        super(animationService, cdr);
43✔
592
        this.differ = this.iterableDiffers.find([]).create(null);
43✔
593
    }
594

595

596
    /** @hidden */
597
    @HostListener('keydown.arrowright', ['$event'])
598
    public onKeydownArrowRight(event) {
599
        if (this.keyboardSupport) {
8✔
600
            event.preventDefault();
5✔
601
            this.next();
5✔
602
            this.focusElement();
5✔
603
        }
604
    }
605

606
    /** @hidden */
607
    @HostListener('keydown.arrowleft', ['$event'])
608
    public onKeydownArrowLeft(event) {
609
        if (this.keyboardSupport) {
5✔
610
            event.preventDefault();
2✔
611
            this.prev();
2✔
612
            this.focusElement();
2✔
613
        }
614
    }
615

616
    /** @hidden */
617
    @HostListener('tap', ['$event'])
618
    public onTap(event) {
619
        // play pause only when tap on slide
620
        if (event.target && event.target.classList.contains('igx-slide')) {
4✔
621
            if (this.isPlaying) {
4✔
622
                if (this.pause) {
1✔
623
                    this.stoppedByInteraction = true;
1✔
624
                }
625
                this.stop();
1✔
626
            } else if (this.stoppedByInteraction) {
3✔
627
                this.play();
1✔
628
            }
629
        }
630
    }
631

632
    /** @hidden */
633
    @HostListener('keydown.home', ['$event'])
634
    public onKeydownHome(event) {
635
        if (this.keyboardSupport && this.slides.length > 0) {
4✔
636
            event.preventDefault();
1✔
637
            this.slides.first.active = true;
1✔
638
            this.focusElement();
1✔
639
        }
640
    }
641

642
    /** @hidden */
643
    @HostListener('keydown.end', ['$event'])
644
    public onKeydownEnd(event) {
645
        if (this.keyboardSupport && this.slides.length > 0) {
4✔
646
            event.preventDefault();
1✔
647
            this.slides.last.active = true;
1✔
648
            this.focusElement();
1✔
649
        }
650
    }
651

652
    /** @hidden */
653
    @HostListener('mouseenter')
654
    public onMouseEnter() {
655
        if (this.pause && this.isPlaying) {
2✔
656
            this.stoppedByInteraction = true;
1✔
657
        }
658
        this.stop();
2✔
659
    }
660

661
    /** @hidden */
662
    @HostListener('mouseleave')
663
    public onMouseLeave() {
664
        if (this.stoppedByInteraction) {
2✔
665
            this.play();
1✔
666
        }
667
    }
668

669
    /** @hidden */
670
    @HostListener('panleft', ['$event'])
671
    public onPanLeft(event) {
672
        if (!this.vertical) {
7✔
673
            this.pan(event);
5✔
674
        }
675
    }
676

677
    /** @hidden */
678
    @HostListener('panright', ['$event'])
679
    public onPanRight(event) {
680
        if (!this.vertical) {
7✔
681
            this.pan(event);
5✔
682
        }
683
    }
684

685
    /** @hidden */
686
    @HostListener('panup', ['$event'])
687
    public onPanUp(event) {
688
        if (this.vertical) {
5✔
689
            this.pan(event);
3✔
690
        }
691
    }
692

693
    /** @hidden */
694
    @HostListener('pandown', ['$event'])
695
    public onPanDown(event) {
696
        if (this.vertical) {
5✔
697
            this.pan(event);
3✔
698
        }
699
    }
700

701
    /**
702
     * @hidden
703
     */
704
    @HostListener('panend', ['$event'])
705
    public onPanEnd(event) {
706
        if (!this.gesturesSupport) {
24✔
707
            return;
2✔
708
        }
709
        event.preventDefault();
22✔
710

711
        const slideSize = this.vertical
22✔
712
            ? this.currentItem.nativeElement.offsetHeight
713
            : this.currentItem.nativeElement.offsetWidth;
714
        const panOffset = (slideSize / 1000);
22✔
715
        const eventDelta = this.vertical ? event.deltaY : event.deltaX;
22✔
716
        const delta = Math.abs(eventDelta) + panOffset < slideSize ? Math.abs(eventDelta) : slideSize - panOffset;
22!
717
        const velocity = Math.abs(event.velocity);
22✔
718
        this.resetSlideStyles(this.currentItem);
22✔
719
        if (this.incomingSlide) {
22✔
720
            this.resetSlideStyles(this.incomingSlide);
12✔
721
            if (slideSize / 2 < delta || velocity > 1) {
12✔
722
                this.incomingSlide.direction = eventDelta < 0 ? Direction.NEXT : Direction.PREV;
8✔
723
                this.incomingSlide.previous = false;
8✔
724

725
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
8!
726
                    delta / slideSize : (slideSize - delta) / slideSize;
727

728
                if (velocity > 1) {
8✔
729
                    this.newDuration = this.defaultAnimationDuration / velocity;
4✔
730
                }
731
                this.incomingSlide.active = true;
8✔
732
            } else {
733
                this.currentItem.direction = eventDelta > 0 ? Direction.NEXT : Direction.PREV;
4✔
734
                this.previousItem = this.incomingSlide;
4✔
735
                this.previousItem.previous = true;
4✔
736
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
4!
737
                    Math.abs((slideSize - delta) / slideSize) : delta / slideSize;
738
                this.playAnimations();
4✔
739
            }
740
        }
741

742
        if (this.stoppedByInteraction) {
22!
743
            this.play();
×
744
        }
745
    }
746

747
    /** @hidden */
748
    public ngAfterContentInit() {
749
        this.slides.changes
43✔
750
            .pipe(takeUntil(this.destroy$))
751
            .subscribe((change: QueryList<IgxSlideComponent>) => this.initSlides(change));
13✔
752

753
        this.initSlides(this.slides);
43✔
754
    }
755

756
    /** @hidden */
757
    public ngOnDestroy() {
758
        this.destroy$.next(true);
44✔
759
        this.destroy$.complete();
44✔
760
        this.destroyed = true;
44✔
761
        if (this.lastInterval) {
44✔
762
            clearInterval(this.lastInterval);
25✔
763
        }
764
    }
765

766
    /** @hidden */
767
    public handleKeydownPrev(event: KeyboardEvent): void {
768
        if (this.platformUtil.isActivationKey(event)) {
2✔
769
            event.preventDefault();
2✔
770
            this.prev();
2✔
771
        }
772
    }
773

774
    /** @hidden */
775
    public handleKeydownNext(event: KeyboardEvent): void {
776
        if (this.platformUtil.isActivationKey(event)) {
2✔
777
            event.preventDefault();
2✔
778
            this.next();
2✔
779
        }
780
    }
781

782
    /** @hidden */
783
    public handleKeyUp(event: KeyboardEvent): void {
784
        if (event.key === this.platformUtil.KEYMAP.TAB) {
5✔
785
            this._hasKeyboardFocusOnIndicators = true;
5✔
786
        }
787
    }
788

789
    /** @hidden */
790
    public handleFocusOut(event: FocusEvent): void {
791
        const target = event.relatedTarget as HTMLElement;
7✔
792

793
        if (!target || !target.classList.contains('igx-carousel-indicators__indicator')) {
7✔
794
            this._hasKeyboardFocusOnIndicators = false;
1✔
795
        }
796
    }
797

798
    /** @hidden */
799
    public handleClick(): void {
800
        this._hasKeyboardFocusOnIndicators = false;
1✔
801
    }
802

803
    /** @hidden */
804
    public handleKeydown(event: KeyboardEvent): void {
805
        if (this.keyboardSupport) {
8!
806
            return;
×
807
        }
808
        const { key } = event;
8✔
809
        const slides = this.slides.toArray();
8✔
810

811
        switch (key) {
8✔
812
            case this.platformUtil.KEYMAP.ARROW_LEFT:
813
                this.dir.rtl ? this.next() : this.prev();
2✔
814
                break;
2✔
815
            case this.platformUtil.KEYMAP.ARROW_RIGHT:
816
                this.dir.rtl ? this.prev() : this.next();
2✔
817
                break;
2✔
818
            case this.platformUtil.KEYMAP.HOME:
819
                event.preventDefault();
2✔
820
                this.select(this.dir.rtl ? last(slides) : first(slides));
2✔
821
                break;
2✔
822
            case this.platformUtil.KEYMAP.END:
823
                event.preventDefault();
2✔
824
                this.select(this.dir.rtl ? first(slides) : last(slides));
2✔
825
                break;
2✔
826
        }
827

828
        this.indicatorsElements[this.current].nativeElement.focus();
8✔
829
    }
830

831
    /**
832
     * Returns the slide corresponding to the provided `index` or null.
833
     * ```typescript
834
     * let slide1 =  this.carousel.get(1);
835
     * ```
836
     *
837
     * @memberOf IgxCarouselComponent
838
     */
839
    public get(index: number): IgxSlideComponent {
840
        return this.slides.find((slide) => slide.index === index);
7,549✔
841
    }
842

843
    /**
844
     * Adds a new slide to the carousel.
845
     * ```typescript
846
     * this.carousel.add(newSlide);
847
     * ```
848
     *
849
     * @memberOf IgxCarouselComponent
850
     */
851
    public add(slide: IgxSlideComponent) {
852
        const newSlides = this.slides.toArray();
3✔
853
        newSlides.push(slide);
3✔
854
        this.slides.reset(newSlides);
3✔
855
        this.slides.notifyOnChanges();
3✔
856
    }
857

858
    /**
859
     * Removes a slide from the carousel.
860
     * ```typescript
861
     * this.carousel.remove(slide);
862
     * ```
863
     *
864
     * @memberOf IgxCarouselComponent
865
     */
866
    public remove(slide: IgxSlideComponent) {
867
        if (slide && slide === this.get(slide.index)) { // check if the requested slide for delete is present in the carousel
4✔
868
            const newSlides = this.slides.toArray();
4✔
869
            newSlides.splice(slide.index, 1);
4✔
870
            this.slides.reset(newSlides);
4✔
871
            this.slides.notifyOnChanges();
4✔
872
        }
873
    }
874

875
    /**
876
     * Kicks in a transition for a given slide with a given `direction`.
877
     * ```typescript
878
     * this.carousel.select(this.carousel.get(2), Direction.NEXT);
879
     * ```
880
     *
881
     * @memberOf IgxCarouselComponent
882
     */
883
    public select(slide: IgxSlideComponent, direction: Direction = Direction.NONE) {
12✔
884
        if (slide && slide !== this.currentItem) {
3,222✔
885
            slide.direction = direction;
3,221✔
886
            slide.active = true;
3,221✔
887
        }
888
    }
889

890
    /**
891
     * Transitions to the next slide in the carousel.
892
     * ```typescript
893
     * this.carousel.next();
894
     * ```
895
     *
896
     * @memberOf IgxCarouselComponent
897
     */
898
    public next() {
899
        const index = this.getNextIndex();
3,199✔
900

901
        if (index === 0 && !this.loop) {
3,199✔
902
            this.stop();
1✔
903
            return;
1✔
904
        }
905
        return this.select(this.get(index), Direction.NEXT);
3,198✔
906
    }
907

908
    /**
909
     * Transitions to the previous slide in the carousel.
910
     * ```typescript
911
     * this.carousel.prev();
912
     * ```
913
     *
914
     * @memberOf IgxCarouselComponent
915
     */
916
    public prev() {
917
        const index = this.getPrevIndex();
13✔
918

919
        if (!this.loop && index === this.total - 1) {
13✔
920
            this.stop();
1✔
921
            return;
1✔
922
        }
923
        return this.select(this.get(index), Direction.PREV);
12✔
924
    }
925

926
    /**
927
     * Resumes playing of the carousel if in paused state.
928
     * No operation otherwise.
929
     * ```typescript
930
     * this.carousel.play();
931
     * }
932
     * ```
933
     *
934
     * @memberOf IgxCarouselComponent
935
     */
936
    public play() {
937
        if (!this.playing) {
61✔
938
            this.playing = true;
47✔
939
            this.carouselPlaying.emit(this);
47✔
940
            this.restartInterval();
47✔
941
            this.stoppedByInteraction = false;
47✔
942
        }
943
    }
944

945
    /**
946
     * Stops slide transitions if the `pause` option is set to `true`.
947
     * No operation otherwise.
948
     * ```typescript
949
     *  this.carousel.stop();
950
     * }
951
     * ```
952
     *
953
     * @memberOf IgxCarouselComponent
954
     */
955
    public stop() {
956
        if (this.pause) {
9✔
957
            this.playing = false;
9✔
958
            this.carouselPaused.emit(this);
9✔
959
            this.resetInterval();
9✔
960
        }
961
    }
962

963
    protected getPreviousElement(): HTMLElement {
964
        return this.previousItem.nativeElement;
2✔
965
    }
966

967
    protected getCurrentElement(): HTMLElement {
968
        return this.currentItem.nativeElement;
3✔
969
    }
970

971
    private resetInterval() {
972
        if (this.lastInterval) {
138✔
973
            clearInterval(this.lastInterval);
70✔
974
            this.lastInterval = null;
70✔
975
        }
976
    }
977

978
    private restartInterval() {
979
        this.resetInterval();
129✔
980

981
        if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
129✔
982
            this.lastInterval = setInterval(() => {
97✔
983
                const tick = +this.interval;
3,180✔
984
                if (this.playing && this.total && !isNaN(tick) && tick > 0) {
3,180!
985
                    this.next();
3,180✔
986
                } else {
987
                    this.stop();
×
988
                }
989
            }, this.interval);
990
        }
991
    }
992

993
    /** @hidden */
994
    public get nextButtonDisabled() {
995
        return !this.loop && this.current === (this.total - 1);
780✔
996
    }
997

998
    /** @hidden */
999
    public get prevButtonDisabled() {
1000
        return !this.loop && this.current === 0;
780✔
1001
    }
1002

1003
    private get indicatorsElements() {
1004
        return this._indicators.toArray();
8✔
1005
    }
1006

1007
    private focusElement() {
1008
        const focusedElement = this.document.activeElement;
9✔
1009

1010
        if (focusedElement.classList.contains('igx-carousel-indicators__indicator')) {
9!
1011
            this.indicatorsElements[this.current].nativeElement.focus();
×
1012
        } else {
1013
            this.focusSlideElement();
9✔
1014
        }
1015
    }
1016

1017
    private getNextIndex(): number {
1018
        return (this.current + 1) % this.total;
3,207✔
1019
    }
1020

1021
    private getPrevIndex(): number {
1022
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
21✔
1023
    }
1024

1025
    private resetSlideStyles(slide: IgxSlideComponent) {
1026
        slide.nativeElement.style.transform = '';
38✔
1027
        slide.nativeElement.style.opacity = '';
38✔
1028
    }
1029

1030
    private pan(event) {
1031
        const slideSize = this.vertical
16✔
1032
            ? this.currentItem.nativeElement.offsetHeight
1033
            : this.currentItem.nativeElement.offsetWidth;
1034
        const panOffset = (slideSize / 1000);
16✔
1035
        const delta = this.vertical ? event.deltaY : event.deltaX;
16✔
1036
        const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
16✔
1037
        const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
16✔
1038

1039
        if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
16✔
1040
            return;
2✔
1041
        }
1042

1043
        if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
14✔
1044
            this.incomingSlide = null;
2✔
1045
            return;
2✔
1046
        }
1047

1048
        event.preventDefault();
12✔
1049
        if (this.isPlaying) {
12!
1050
            this.stoppedByInteraction = true;
×
1051
            this.stop();
×
1052
        }
1053

1054
        if (this.previousItem && this.previousItem.previous) {
12✔
1055
            this.previousItem.previous = false;
4✔
1056
        }
1057
        this.finishAnimations();
12✔
1058

1059
        if (this.incomingSlide) {
12✔
1060
            if (index !== this.incomingSlide.index) {
8✔
1061
                this.resetSlideStyles(this.incomingSlide);
4✔
1062
                this.incomingSlide.previous = false;
4✔
1063
                this.incomingSlide = this.get(index);
4✔
1064
            }
1065
        } else {
1066
            this.incomingSlide = this.get(index);
4✔
1067
        }
1068
        this.incomingSlide.previous = true;
12✔
1069

1070
        if (this.animationType === CarouselAnimationType.fade) {
12!
1071
            this.currentItem.nativeElement.style.opacity = `${Math.abs(offset) / slideSize}`;
×
1072
        } else {
1073
            this.currentItem.nativeElement.style.transform = this.vertical
12✔
1074
                ? `translateY(${delta}px)`
1075
                : `translateX(${delta}px)`;
1076
            this.incomingSlide.nativeElement.style.transform = this.vertical
12✔
1077
                ? `translateY(${offset}px)`
1078
                : `translateX(${offset}px)`;
1079
        }
1080
    }
1081

1082
    private unsubscriber(slide: IgxSlideComponent) {
1083
        return merge(this.destroy$, slide.isDestroyed);
179✔
1084
    }
1085

1086
    private onSlideActivated(slide: IgxSlideComponent) {
1087
        if (slide.active && slide !== this.currentItem) {
103✔
1088
            if (slide.direction === Direction.NONE) {
53✔
1089
                const newIndex = slide.index;
11✔
1090
                slide.direction = newIndex > this.current ? Direction.NEXT : Direction.PREV;
11✔
1091
            }
1092

1093
            if (this.currentItem) {
53✔
1094
                if (this.previousItem && this.previousItem.previous) {
41!
1095
                    this.previousItem.previous = false;
×
1096
                }
1097
                this.currentItem.direction = slide.direction;
41✔
1098
                this.currentItem.active = false;
41✔
1099

1100
                this.previousItem = this.currentItem;
41✔
1101
                this.currentItem = slide;
41✔
1102
                this.triggerAnimations();
41✔
1103
            } else {
1104
                this.currentItem = slide;
12✔
1105
            }
1106
            this.slideChanged.emit({ carousel: this, slide });
53✔
1107
            this.restartInterval();
53✔
1108
            this.cdr.markForCheck();
53✔
1109
        }
1110
    }
1111

1112

1113
    private finishAnimations() {
1114
        if (this.animationStarted(this.leaveAnimationPlayer)) {
12!
1115
            this.leaveAnimationPlayer.finish();
×
1116
        }
1117

1118
        if (this.animationStarted(this.enterAnimationPlayer)) {
12!
1119
            this.enterAnimationPlayer.finish();
×
1120
        }
1121
    }
1122

1123
    private initSlides(change: QueryList<IgxSlideComponent>) {
1124
        const diff = this.differ.diff(change.toArray());
56✔
1125
        if (diff) {
56✔
1126
            this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
214✔
1127
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
56✔
1128
                const slide = record.item;
179✔
1129
                slide.total = this.total;
179✔
1130
                this.slideAdded.emit({ carousel: this, slide });
179✔
1131
                if (slide.active) {
179✔
1132
                    this.currentItem = slide;
17✔
1133
                }
1134
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
179✔
1135
            });
1136

1137
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
56✔
1138
                const slide = record.item;
10✔
1139
                this.slideRemoved.emit({ carousel: this, slide });
10✔
1140
                if (slide.active) {
10✔
1141
                    slide.active = false;
3✔
1142
                    this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
3✔
1143
                }
1144
            });
1145

1146
            this.updateSlidesSelection();
56✔
1147
        }
1148
    }
1149

1150
    private updateSlidesSelection() {
1151
        if (this.platformUtil.isBrowser) {
56✔
1152
            requestAnimationFrame(() => {
56✔
1153
                if (this.currentItem) {
56✔
1154
                    this.currentItem.active = true;
32✔
1155
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
126✔
1156
                    activeSlides.forEach(slide => slide.active = false);
32✔
1157
                } else if (this.total) {
24✔
1158
                    this.slides.first.active = true;
23✔
1159
                }
1160
                this.play();
56✔
1161
            });
1162
        }
1163
    }
1164
    private focusSlideElement() {
1165
        if (this.leaveAnimationPlayer) {
9!
1166
            this.leaveAnimationPlayer.animationEnd
×
1167
                .pipe(takeUntil(this.destroy$))
1168
                .subscribe(() => {
1169
                    this.slides.find(s => s.active).nativeElement.focus();
×
1170
                });
1171
        } else {
1172
            requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
35✔
1173
        }
1174
    }
1175

1176
}
1177

1178
export interface ISlideEventArgs extends IBaseEventArgs {
1179
    carousel: IgxCarouselComponent;
1180
    slide: IgxSlideComponent;
1181
}
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