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

IgniteUI / igniteui-angular / 30354093041

28 Jul 2026 11:15AM UTC coverage: 90.184% (+0.04%) from 90.141%
30354093041

push

github

web-flow
Remove Hammer.js dependency, replace with native Pointer/Touch Events (#17359)

* feat(*): remove Hammer.js dependency, replace with native pointer events.
* refactor(*): add zoneless IgxTouchManager, drop NgZone from gestures
* fix(nav-drawer): applying pan-related fixes
* test(time-picker): add pan-to-scroll gesture spec
* fix(nav-drawer): adding correct gesture detach logic
* fix(list): ensuring no panStart list item event on touchmanager panstart
* fix(*): making touch ssr no-op, removing double as ios doesn't need it anymore
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Stamen Stoychev <SStoychev@infragistics.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

14953 of 17412 branches covered (85.88%)

Branch coverage included in aggregate %.

120 of 141 new or added lines in 5 files covered. (85.11%)

9 existing lines in 4 files now uncovered.

30067 of 32508 relevant lines covered (92.49%)

37545.53 hits per line

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

90.93
/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts
1
import { NgClass, NgTemplateOutlet } from '@angular/common';
2
import { AfterContentInit, Component, ContentChild, ContentChildren, ElementRef, EventEmitter, HostBinding, HostListener, Input, IterableChangeRecord, IterableDiffer, IterableDiffers, OnDestroy, Output, QueryList, TemplateRef, ViewChild, ViewChildren, booleanAttribute, inject, ChangeDetectionStrategy } from '@angular/core';
3
import { merge, Subject } from 'rxjs';
4
import { takeUntil } from 'rxjs/operators';
5
import { CarouselResourceStringsEN, ICarouselResourceStrings, isLeftToRight } from 'igniteui-angular/core';
6
import { first, IBaseEventArgs, IgxTouchManager, last, PlatformUtil } from 'igniteui-angular/core';
7
import { CarouselAnimationDirection, IgxCarouselComponentBase } from './carousel-base';
8
import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives';
9
import { IgxSlideComponent } from './slide.component';
10
import { IgxIconComponent } from 'igniteui-angular/icon';
11
import { IgxButtonDirective } from 'igniteui-angular/directives';
12
import { getCurrentResourceStrings, onResourceChangeHandle } from 'igniteui-angular/core';
13
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
14

15
let NEXT_ID = 0;
3✔
16

17
/**
18
 * **Ignite UI for Angular Carousel** -
19
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel.html)
20
 *
21
 * The Ignite UI Carousel is used to browse or navigate through a collection of slides. Slides can contain custom
22
 * content such as images or cards and be used for things such as on-boarding tutorials or page-based interfaces.
23
 * It can be used as a separate fullscreen element or inside another component.
24
 *
25
 * Example:
26
 * ```html
27
 * <igx-carousel>
28
 *   <igx-slide>
29
 *     <h3>First Slide Header</h3>
30
 *     <p>First slide Content</p>
31
 *   <igx-slide>
32
 *   <igx-slide>
33
 *     <h3>Second Slide Header</h3>
34
 *     <p>Second Slide Content</p>
35
 * </igx-carousel>
36
 * ```
37
 */
38
@Component({
39
    selector: 'igx-carousel',
40
    templateUrl: 'carousel.component.html',
41
    styles: [`
42
    :host {
43
        display: block;
44
        outline-style: none;
45
    }`],
46
    changeDetection: ChangeDetectionStrategy.Eager,
47
    imports: [IgxButtonDirective, IgxIconComponent, NgClass, NgTemplateOutlet]
48
})
49
export class IgxCarouselComponent extends IgxCarouselComponentBase implements OnDestroy, AfterContentInit {
3✔
50
    private element = inject(ElementRef);
45✔
51
    private iterableDiffers = inject(IterableDiffers);
45✔
52
    private platformUtil = inject(PlatformUtil);
45✔
53

54

55

56
    /**
57
     * Sets the `id` of the carousel.
58
     * If not set, the `id` of the first carousel component will be `"igx-carousel-0"`.
59
     * ```html
60
     * <igx-carousel id="my-first-carousel"></igx-carousel>
61
     * ```
62
     *
63
     * @memberof IgxCarouselComponent
64
     */
65
    @HostBinding('attr.id')
66
    @Input()
67
    public id = `igx-carousel-${NEXT_ID++}`;
45✔
68
    /**
69
     * Returns the `role` attribute of the carousel.
70
     * ```typescript
71
     * let carouselRole =  this.carousel.role;
72
     * ```
73
     *
74
     * @memberof IgxCarouselComponent
75
     */
76
    @HostBinding('attr.role') public role = 'region';
45✔
77

78
    /** @hidden */
79
    @HostBinding('attr.aria-roledescription')
80
    public roleDescription = 'carousel';
45✔
81

82
    /** @hidden */
83
    @HostBinding('attr.aria-labelledby')
84
    public get labelId() {
85
        return this.showIndicatorsLabel ? `${this.id}-label` : null;
411✔
86
    }
87

88
    /** @hidden */
89
    @HostBinding('class.igx-carousel--vertical')
90
    public get isVertical(): boolean {
91
        return this.vertical;
409✔
92
    }
93

94
    /**
95
     * Returns the class of the carousel component.
96
     * ```typescript
97
     * let class =  this.carousel.cssClass;
98
     * ```
99
     *
100
     * @memberof IgxCarouselComponent
101
     */
102
    @HostBinding('class.igx-carousel')
103
    public cssClass = 'igx-carousel';
45✔
104

105
    /**
106
     * Gets the `touch-action` style of the `list item`.
107
     * ```typescript
108
     * let touchAction = this.listItem.touchAction;
109
     * ```
110
     */
111
    @HostBinding('style.touch-action')
112
    public get touchAction() {
113
        return this.gesturesSupport ? 'pan-y' : 'auto';
409✔
114
    }
115

116
    /**
117
     * Sets whether the carousel should `loop` back to the first slide after reaching the last slide.
118
     * Default value is `true`.
119
     * ```html
120
     * <igx-carousel [loop]="false"></igx-carousel>
121
     * ```
122
     *
123
     * @memberOf IgxCarouselComponent
124
     */
125
    @Input({ transform: booleanAttribute }) public loop = true;
45✔
126

127
    /**
128
     * Sets whether the carousel will `pause` the slide transitions on user interactions.
129
     * Default value is `true`.
130
     * ```html
131
     *  <igx-carousel [pause]="false"></igx-carousel>
132
     * ```
133
     *
134
     * @memberOf IgxCarouselComponent
135
     */
136
    @Input({ transform: booleanAttribute }) public pause = true;
45✔
137

138
    /**
139
     * Controls whether the carousel should render the left/right `navigation` buttons.
140
     * Default value is `true`.
141
     * ```html
142
     * <igx-carousel [navigation]="false"></igx-carousel>
143
     * ```
144
     *
145
     * @memberOf IgxCarouselComponent
146
     */
147
    @Input({ transform: booleanAttribute }) public navigation = true;
45✔
148

149
    /**
150
     * Controls whether the carousel should render the indicators.
151
     * Default value is `true`.
152
     * ```html
153
     * <igx-carousel [indicators]="false"></igx-carousel>
154
     * ```
155
     *
156
     * @memberOf IgxCarouselComponent
157
     */
158
    @Input({ transform: booleanAttribute }) public indicators = true;
45✔
159

160

161
    /**
162
     * Controls whether the carousel has vertical alignment.
163
     * Default value is `false`.
164
     * ```html
165
     * <igx-carousel [vertical]="true"></igx-carousel>
166
     * ```
167
     *
168
     * @memberOf IgxCarouselComponent
169
     */
170
    @Input({ transform: booleanAttribute }) public override vertical = false;
45✔
171

172
    /**
173
     * Controls whether the carousel should support gestures.
174
     * Default value is `true`.
175
     * ```html
176
     * <igx-carousel [gesturesSupport]="false"></igx-carousel>
177
     * ```
178
     *
179
     * @memberOf IgxCarouselComponent
180
     */
181
    @Input({ transform: booleanAttribute }) public gesturesSupport = true;
45✔
182

183
    /**
184
     * Controls the maximum indexes that can be shown.
185
     * Default value is `10`.
186
     * ```html
187
     * <igx-carousel [maximumIndicatorsCount]="5"></igx-carousel>
188
     * ```
189
     *
190
     * @memberOf IgxCarouselComponent
191
     */
192
    @Input() public maximumIndicatorsCount = 10;
45✔
193

194
    /**
195
     * Gets/sets the display mode of carousel indicators. It can be `start` or `end`.
196
     * Default value is `end`.
197
     * ```html
198
     * <igx-carousel indicatorsOrientation="start">
199
     * <igx-carousel>
200
     * ```
201
     *
202
     * @memberOf IgxCarouselComponent
203
     */
204
    @Input() public indicatorsOrientation: CarouselIndicatorsOrientation = CarouselIndicatorsOrientation.end;
45✔
205

206
    /**
207
     * Gets/sets the animation type of carousel.
208
     * Default value is `slide`.
209
     * ```html
210
     * <igx-carousel animationType="none">
211
     * <igx-carousel>
212
     * ```
213
     *
214
     * @memberOf IgxCarouselComponent
215
     */
216
    @Input() public override animationType: CarouselAnimationType = CarouselAnimationType.slide;
45✔
217

218
    /**
219
     * The custom template, if any, that should be used when rendering carousel indicators
220
     *
221
     * ```typescript
222
     * // Set in typescript
223
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
224
     * myComponent.carousel.indicatorTemplate = myCustomTemplate;
225
     * ```
226
     * ```html
227
     * <!-- Set in markup -->
228
     *  <igx-carousel #carousel>
229
     *      ...
230
     *      <ng-template igxCarouselIndicator let-slide>
231
     *         <igx-icon *ngIf="slide.active">brightness_7</igx-icon>
232
     *         <igx-icon *ngIf="!slide.active">brightness_5</igx-icon>
233
     *      </ng-template>
234
     *  </igx-carousel>
235
     * ```
236
     */
237
    @ContentChild(IgxCarouselIndicatorDirective, { read: TemplateRef, static: false })
238
    public indicatorTemplate: TemplateRef<any> = null;
45✔
239

240
    /**
241
     * The custom template, if any, that should be used when rendering carousel next button
242
     *
243
     * ```typescript
244
     * // Set in typescript
245
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
246
     * myComponent.carousel.nextButtonTemplate = myCustomTemplate;
247
     * ```
248
     * ```html
249
     * <!-- Set in markup -->
250
     *  <igx-carousel #carousel>
251
     *      ...
252
     *      <ng-template igxCarouselNextButton let-disabled>
253
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
254
     *              <igx-icon name="add"></igx-icon>
255
     *          </button>
256
     *      </ng-template>
257
     *  </igx-carousel>
258
     * ```
259
     */
260
    @ContentChild(IgxCarouselNextButtonDirective, { read: TemplateRef, static: false })
261
    public nextButtonTemplate: TemplateRef<any> = null;
45✔
262

263
    /**
264
     * The custom template, if any, that should be used when rendering carousel previous button
265
     *
266
     * ```typescript
267
     * // Set in typescript
268
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
269
     * myComponent.carousel.prevButtonTemplate = myCustomTemplate;
270
     * ```
271
     * ```html
272
     * <!-- Set in markup -->
273
     *  <igx-carousel #carousel>
274
     *      ...
275
     *      <ng-template igxCarouselPrevButton let-disabled>
276
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
277
     *              <igx-icon name="remove"></igx-icon>
278
     *          </button>
279
     *      </ng-template>
280
     *  </igx-carousel>
281
     * ```
282
     */
283
    @ContentChild(IgxCarouselPrevButtonDirective, { read: TemplateRef, static: false })
284
    public prevButtonTemplate: TemplateRef<any> = null;
45✔
285

286
    /**
287
     * The collection of `slides` currently in the carousel.
288
     * ```typescript
289
     * let slides: QueryList<IgxSlideComponent> = this.carousel.slides;
290
     * ```
291
     *
292
     * @memberOf IgxCarouselComponent
293
     */
294
    @ContentChildren(IgxSlideComponent)
295
    public slides: QueryList<IgxSlideComponent>;
296

297
    /**
298
     * An event that is emitted after a slide transition has happened.
299
     * Provides references to the carousel and slide components as event arguments.
300
     * ```html
301
     * <igx-carousel (slideChanged)="slideChanged($event)"></igx-carousel>
302
     * ```
303
     *
304
     * @memberOf IgxCarouselComponent
305
     */
306
    @Output() public slideChanged = new EventEmitter<ISlideEventArgs>();
45✔
307

308
    /**
309
     * An event that is emitted after a slide has been added to the carousel.
310
     * Provides references to the carousel and slide components as event arguments.
311
     * ```html
312
     * <igx-carousel (slideAdded)="slideAdded($event)"></igx-carousel>
313
     * ```
314
     *
315
     * @memberOf IgxCarouselComponent
316
     */
317
    @Output() public slideAdded = new EventEmitter<ISlideEventArgs>();
45✔
318

319
    /**
320
     * An event that is emitted after a slide has been removed from the carousel.
321
     * Provides references to the carousel and slide components as event arguments.
322
     * ```html
323
     * <igx-carousel (slideRemoved)="slideRemoved($event)"></igx-carousel>
324
     * ```
325
     *
326
     * @memberOf IgxCarouselComponent
327
     */
328
    @Output() public slideRemoved = new EventEmitter<ISlideEventArgs>();
45✔
329

330
    /**
331
     * An event that is emitted after the carousel has been paused.
332
     * Provides a reference to the carousel as an event argument.
333
     * ```html
334
     * <igx-carousel (carouselPaused)="carouselPaused($event)"></igx-carousel>
335
     * ```
336
     *
337
     * @memberOf IgxCarouselComponent
338
     */
339
    @Output() public carouselPaused = new EventEmitter<IgxCarouselComponent>();
45✔
340

341
    /**
342
     * An event that is emitted after the carousel has resumed transitioning between `slides`.
343
     * Provides a reference to the carousel as an event argument.
344
     * ```html
345
     * <igx-carousel (carouselPlaying)="carouselPlaying($event)"></igx-carousel>
346
     * ```
347
     *
348
     * @memberOf IgxCarouselComponent
349
     */
350
    @Output() public carouselPlaying = new EventEmitter<IgxCarouselComponent>();
45✔
351

352
    @ViewChild('defaultIndicator', { read: TemplateRef, static: true })
353
    private defaultIndicator: TemplateRef<any>;
354

355
    @ViewChild('defaultNextButton', { read: TemplateRef, static: true })
356
    private defaultNextButton: TemplateRef<any>;
357

358
    @ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
359
    private defaultPrevButton: TemplateRef<any>;
360

361
    @ViewChildren('indicators', { read: ElementRef })
362
    private _indicators: QueryList<ElementRef<HTMLDivElement>>;
363

364
    /**
365
     * @hidden
366
     * @internal
367
     */
368
    public stoppedByInteraction: boolean;
369
    protected override currentItem: IgxSlideComponent;
370
    protected override previousItem: IgxSlideComponent;
371
    private _interval: number;
372
    private _resourceStrings: ICarouselResourceStrings = null;
45✔
373
    private _defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN);
45✔
374
    private lastInterval: any;
375
    private playing: boolean;
376
    private destroyed: boolean;
377
    private destroy$ = new Subject<any>();
45✔
378
    private differ: IterableDiffer<IgxSlideComponent> | null = null;
45✔
379
    private incomingSlide: IgxSlideComponent;
380
    private _hasKeyboardFocusOnIndicators = false;
45✔
381

382
    /**
383
     * An accessor that sets the resource strings.
384
     * By default it uses EN resources.
385
     */
386
    @Input()
387
    public set resourceStrings(value: ICarouselResourceStrings) {
388
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
389
    }
390

391
    /**
392
     * An accessor that returns the resource strings.
393
     */
394
    public get resourceStrings(): ICarouselResourceStrings {
395
        return this._resourceStrings || this._defaultResourceStrings;
3,994✔
396
    }
397

398
    /** @hidden */
399
    public get getIndicatorTemplate(): TemplateRef<any> {
400
        if (this.indicatorTemplate) {
1,593✔
401
            return this.indicatorTemplate;
24✔
402
        }
403
        return this.defaultIndicator;
1,569✔
404
    }
405

406
    /** @hidden */
407
    public get getNextButtonTemplate(): TemplateRef<any> {
408
        if (this.nextButtonTemplate) {
403✔
409
            return this.nextButtonTemplate;
6✔
410
        }
411

412
        return this.defaultNextButton
397✔
413
    }
414

415
    /** @hidden */
416
    public get getPrevButtonTemplate(): TemplateRef<any> {
417
        if (this.prevButtonTemplate) {
403✔
418
            return this.prevButtonTemplate;
6✔
419
        }
420

421
        return this.defaultPrevButton
397✔
422
    }
423

424
    /** @hidden */
425
    public get indicatorsClass() {
426
        return {
405✔
427
            ['igx-carousel-indicators--focused']: this._hasKeyboardFocusOnIndicators,
428
            [`igx-carousel-indicators--${this.getIndicatorsClass()}`]: true
429
        };
430
    }
431

432
    /** @hidden */
433
    public get showIndicators(): boolean {
434
        return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
409✔
435
    }
436

437
    /** @hidden */
438
    public get showIndicatorsLabel(): boolean {
439
        return this.indicators && this.total > this.maximumIndicatorsCount;
820✔
440
    }
441

442
    /** @hidden */
443
    public get getCarouselLabel() {
444
        return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
2✔
445
    }
446

447
    /**
448
     * Returns the total number of `slides` in the carousel.
449
     * ```typescript
450
     * let slideCount =  this.carousel.total;
451
     * ```
452
     *
453
     * @memberOf IgxCarouselComponent
454
     */
455
    public get total(): number {
456
        return this.slides?.length;
9,312✔
457
    }
458

459
    /**
460
     * The index of the slide being currently shown.
461
     * ```typescript
462
     * let currentSlideNumber =  this.carousel.current;
463
     * ```
464
     *
465
     * @memberOf IgxCarouselComponent
466
     */
467
    public get current(): number {
468
        return !this.currentItem ? 0 : this.currentItem.index;
2,401✔
469
    }
470

471
    /**
472
     * Returns a boolean indicating if the carousel is playing.
473
     * ```typescript
474
     * let isPlaying =  this.carousel.isPlaying;
475
     * ```
476
     *
477
     * @memberOf IgxCarouselComponent
478
     */
479
    public get isPlaying(): boolean {
480
        return this.playing;
36✔
481
    }
482

483
    /**
484
     * Returns а boolean indicating if the carousel is destroyed.
485
     * ```typescript
486
     * let isDestroyed =  this.carousel.isDestroyed;
487
     * ```
488
     *
489
     * @memberOf IgxCarouselComponent
490
     */
491
    public get isDestroyed(): boolean {
492
        return this.destroyed;
1✔
493
    }
494
    /**
495
     * Returns a reference to the carousel element in the DOM.
496
     * ```typescript
497
     * let nativeElement =  this.carousel.nativeElement;
498
     * ```
499
     *
500
     * @memberof IgxCarouselComponent
501
     */
502
    public get nativeElement(): any {
503
        return this.element.nativeElement;
27✔
504
    }
505

506
    /**
507
     * Returns the time `interval` in milliseconds before the slide changes.
508
     * ```typescript
509
     * let timeInterval = this.carousel.interval;
510
     * ```
511
     *
512
     * @memberof IgxCarouselComponent
513
     */
514
    @Input()
515
    public get interval(): number {
516
        return this._interval;
2,826✔
517
    }
518

519
    /**
520
     * Sets the time `interval` in milliseconds before the slide changes.
521
     * If not set, the carousel will not change `slides` automatically.
522
     * ```html
523
     * <igx-carousel [interval]="1000"></igx-carousel>
524
     * ```
525
     *
526
     * @memberof IgxCarouselComponent
527
     */
528
    public set interval(value: number) {
529
        this._interval = +value;
29✔
530
        this.restartInterval();
29✔
531
    }
532

533
    constructor() {
534
        super();
45✔
535
        this.differ = this.iterableDiffers.find([]).create(null);
45✔
536
        onResourceChangeHandle(this.destroy$, () => {
45✔
537
            this._defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN, false);
×
538
        }, this);
539
    }
540

541
    /** @hidden */
542
    public onTap(event) {
543
        // Play/pause only when the tap lands on a slide (or its content),
544
        // not on the navigation buttons or indicators.
545
        const slide = (event.target as Element)?.closest?.('.igx-slide');
6✔
546

547
        if (slide) {
6✔
548
            if (this.isPlaying) {
6✔
549
                if (this.pause) {
2✔
550
                    this.stoppedByInteraction = true;
2✔
551
                }
552
                this.stop();
2✔
553
            } else if (this.stoppedByInteraction) {
4✔
554
                this.play();
2✔
555
            }
556
        }
557
    }
558

559
    /** @hidden */
560
    @HostListener('mouseenter')
561
    public onMouseEnter() {
562
        if (this.pause && this.isPlaying) {
2✔
563
            this.stoppedByInteraction = true;
1✔
564
        }
565
        this.stop();
2✔
566
    }
567

568
    /** @hidden */
569
    @HostListener('mouseleave')
570
    public onMouseLeave() {
571
        if (this.stoppedByInteraction) {
2✔
572
            this.play();
1✔
573
        }
574
    }
575

576
    /** @hidden */
577
    public onPanLeft(event) {
578
        if (!this.vertical) {
7✔
579
            this.pan(event);
5✔
580
        }
581
    }
582

583
    /** @hidden */
584
    public onPanRight(event) {
585
        if (!this.vertical) {
7✔
586
            this.pan(event);
5✔
587
        }
588
    }
589

590
    /** @hidden */
591
    public onPanUp(event) {
592
        if (this.vertical) {
5✔
593
            this.pan(event);
3✔
594
        }
595
    }
596

597
    /** @hidden */
598
    public onPanDown(event) {
599
        if (this.vertical) {
5✔
600
            this.pan(event);
3✔
601
        }
602
    }
603

604
    /**
605
     * @hidden
606
     */
607
    public onPanEnd(event) {
608
        if (!this.gesturesSupport) {
24✔
609
            return;
2✔
610
        }
611
        event.preventDefault();
22✔
612

613
        const slideSize = this.vertical
22✔
614
            ? this.currentItem.nativeElement.offsetHeight
615
            : this.currentItem.nativeElement.offsetWidth;
616
        const panOffset = (slideSize / 1000);
22✔
617
        const eventDelta = this.vertical ? event.deltaY : event.deltaX;
22✔
618
        const delta = Math.abs(eventDelta) + panOffset < slideSize ? Math.abs(eventDelta) : slideSize - panOffset;
22!
619
        const velocity = Math.abs(event.velocity);
22✔
620
        this.resetSlideStyles(this.currentItem);
22✔
621
        if (this.incomingSlide) {
22✔
622
            this.resetSlideStyles(this.incomingSlide);
12✔
623
            if (slideSize / 2 < delta || velocity > 1) {
12✔
624
                this.incomingSlide.direction = eventDelta < 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
8✔
625
                this.incomingSlide.previous = false;
8✔
626

627
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
8!
628
                    delta / slideSize : (slideSize - delta) / slideSize;
629

630
                if (velocity > 1) {
8✔
631
                    this.newDuration = this.defaultAnimationDuration / velocity;
4✔
632
                }
633
                this.incomingSlide.active = true;
8✔
634
            } else {
635
                this.currentItem.direction = eventDelta > 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
4✔
636
                this.previousItem = this.incomingSlide;
4✔
637
                this.previousItem.previous = true;
4✔
638
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
4!
639
                    Math.abs((slideSize - delta) / slideSize) : delta / slideSize;
640
                this.playAnimations();
4✔
641
            }
642
        }
643

644
        if (this.stoppedByInteraction) {
22!
645
            this.play();
×
646
        }
647
    }
648

649
    /** @hidden */
650
    public ngAfterContentInit() {
651
        this.slides.changes
45✔
652
            .pipe(takeUntil(this.destroy$))
653
            .subscribe((change: QueryList<IgxSlideComponent>) => this.initSlides(change));
14✔
654

655
        this.initSlides(this.slides);
45✔
656
        this.registerGestureEvents();
45✔
657
    }
658

659
    /** @hidden */
660
    public override ngOnDestroy() {
661
        super.ngOnDestroy();
47✔
662
        this.destroy$.next(true);
47✔
663
        this.destroy$.complete();
47✔
664
        this.destroyed = true;
47✔
665
        if (this.lastInterval) {
47✔
666
            clearInterval(this.lastInterval);
25✔
667
        }
668
        this._gestures?.destroy();
47✔
669
    }
670

671
    /** @hidden */
672
    public handleKeydownPrev(event: KeyboardEvent): void {
673
        if (this.platformUtil.isActivationKey(event)) {
2✔
674
            event.preventDefault();
2✔
675
            this.prev();
2✔
676
        }
677
    }
678

679
    /** @hidden */
680
    public handleKeydownNext(event: KeyboardEvent): void {
681
        if (this.platformUtil.isActivationKey(event)) {
2✔
682
            event.preventDefault();
2✔
683
            this.next();
2✔
684
        }
685
    }
686

687
    /** @hidden */
688
    public handleKeyUp(event: KeyboardEvent): void {
689
        if (event.key === this.platformUtil.KEYMAP.TAB) {
6✔
690
            this._hasKeyboardFocusOnIndicators = true;
6✔
691
        }
692
    }
693

694
    /** @hidden */
695
    public handleFocusOut(event: FocusEvent): void {
696
        const target = event.relatedTarget as HTMLElement;
14✔
697

698
        if (!target || !target.classList.contains('igx-carousel-indicators__indicator')) {
14✔
699
            this._hasKeyboardFocusOnIndicators = false;
1✔
700
        }
701
    }
702

703
    /** @hidden */
704
    public handleClick(): void {
705
        this._hasKeyboardFocusOnIndicators = false;
1✔
706
    }
707

708
    /** @hidden */
709
    public handleKeydown(event: KeyboardEvent): void {
710
        const { key } = event;
16✔
711
        const slides = this.slides.toArray();
16✔
712
        const isRTL = !isLeftToRight(this.nativeElement);
16✔
713

714
        switch (key) {
16✔
715
            case this.platformUtil.KEYMAP.ARROW_LEFT:
716
                isRTL ? this.next() : this.prev();
4✔
717
                break;
4✔
718
            case this.platformUtil.KEYMAP.ARROW_RIGHT:
719
                isRTL ? this.prev() : this.next();
6✔
720
                break;
6✔
721
            case this.platformUtil.KEYMAP.HOME:
722
                event.preventDefault();
3✔
723
                this.select(isRTL ? last(slides) : first(slides));
3✔
724
                break;
3✔
725
            case this.platformUtil.KEYMAP.END:
726
                event.preventDefault();
3✔
727
                this.select(isRTL ? first(slides) : last(slides));
3✔
728
                break;
3✔
729
        }
730

731
        this.indicatorsElements[this.current].nativeElement.focus();
16✔
732
    }
733

734
    /**
735
     * Returns the slide corresponding to the provided `index` or null.
736
     * ```typescript
737
     * let slide1 =  this.carousel.get(1);
738
     * ```
739
     *
740
     * @memberOf IgxCarouselComponent
741
     */
742
    public get(index: number): IgxSlideComponent {
743
        return this.slides.find((slide) => slide.index === index);
5,036✔
744
    }
745

746
    /**
747
     * Adds a new slide to the carousel.
748
     * ```typescript
749
     * this.carousel.add(newSlide);
750
     * ```
751
     *
752
     * @memberOf IgxCarouselComponent
753
     */
754
    public add(slide: IgxSlideComponent) {
755
        const newSlides = this.slides.toArray();
3✔
756
        newSlides.push(slide);
3✔
757
        this.slides.reset(newSlides);
3✔
758
        this.slides.notifyOnChanges();
3✔
759
    }
760

761
    /**
762
     * Removes a slide from the carousel.
763
     * ```typescript
764
     * this.carousel.remove(slide);
765
     * ```
766
     *
767
     * @memberOf IgxCarouselComponent
768
     */
769
    public remove(slide: IgxSlideComponent) {
770
        if (slide && slide === this.get(slide.index)) { // check if the requested slide for delete is present in the carousel
4✔
771
            const newSlides = this.slides.toArray();
4✔
772
            newSlides.splice(slide.index, 1);
4✔
773
            this.slides.reset(newSlides);
4✔
774
            this.slides.notifyOnChanges();
4✔
775
        }
776
    }
777

778
    /**
779
     * Switches to the passed-in slide with a given `direction`.
780
     * ```typescript
781
     * const slide = this.carousel.get(2);
782
     * this.carousel.select(slide, CarouselAnimationDirection.NEXT);
783
     * ```
784
     *
785
     * @memberOf IgxCarouselComponent
786
     */
787
    public select(slide: IgxSlideComponent, direction?: CarouselAnimationDirection): void;
788
    /**
789
     * Switches to slide by index with a given `direction`.
790
     * ```typescript
791
     * this.carousel.select(2, CarouselAnimationDirection.NEXT);
792
     * ```
793
     *
794
     * @memberOf IgxCarouselComponent
795
     */
796
    public select(index: number, direction?: CarouselAnimationDirection): void;
797
    public select(slideOrIndex: IgxSlideComponent | number, direction: CarouselAnimationDirection = CarouselAnimationDirection.NONE): void {
16✔
798
        const slide = typeof slideOrIndex === 'number'
2,134✔
799
            ? this.get(slideOrIndex)
800
            : slideOrIndex;
801

802
        if (slide && slide !== this.currentItem) {
2,134✔
803
            slide.direction = direction;
2,131✔
804
            slide.active = true;
2,131✔
805
        }
806
    }
807

808
    /**
809
     * Transitions to the next slide in the carousel.
810
     * ```typescript
811
     * this.carousel.next();
812
     * ```
813
     *
814
     * @memberOf IgxCarouselComponent
815
     */
816
    public next() {
817
        const index = this.getNextIndex();
2,107✔
818

819
        if (index === 0 && !this.loop) {
2,107✔
820
            this.stop();
1✔
821
            return;
1✔
822
        }
823
        return this.select(this.get(index), CarouselAnimationDirection.NEXT);
2,106✔
824
    }
825

826
    /**
827
     * Transitions to the previous slide in the carousel.
828
     * ```typescript
829
     * this.carousel.prev();
830
     * ```
831
     *
832
     * @memberOf IgxCarouselComponent
833
     */
834
    public prev() {
835
        const index = this.getPrevIndex();
13✔
836

837
        if (!this.loop && index === this.total - 1) {
13✔
838
            this.stop();
1✔
839
            return;
1✔
840
        }
841
        return this.select(this.get(index), CarouselAnimationDirection.PREV);
12✔
842
    }
843

844
    /**
845
     * Resumes playing of the carousel if in paused state.
846
     * No operation otherwise.
847
     * ```typescript
848
     * this.carousel.play();
849
     * }
850
     * ```
851
     *
852
     * @memberOf IgxCarouselComponent
853
     */
854
    public play() {
855
        if (!this.playing) {
66✔
856
            this.playing = true;
50✔
857
            this.carouselPlaying.emit(this);
50✔
858
            this.restartInterval();
50✔
859
            this.stoppedByInteraction = false;
50✔
860
        }
861
    }
862

863
    /**
864
     * Stops slide transitions if the `pause` option is set to `true`.
865
     * No operation otherwise.
866
     * ```typescript
867
     *  this.carousel.stop();
868
     * }
869
     * ```
870
     *
871
     * @memberOf IgxCarouselComponent
872
     */
873
    public stop() {
874
        if (this.pause) {
10✔
875
            this.playing = false;
10✔
876
            this.carouselPaused.emit(this);
10✔
877
            this.resetInterval();
10✔
878
        }
879
    }
880

881
    protected getPreviousElement(): HTMLElement {
882
        return this.previousItem.nativeElement;
3✔
883
    }
884

885
    protected getCurrentElement(): HTMLElement {
886
        return this.currentItem.nativeElement;
4✔
887
    }
888

889
    private _gestures: IgxTouchManager | null = null;
45✔
890

891
    private registerGestureEvents() {
892
        if (!this.gesturesSupport || !this.platformUtil.isBrowser) {
45!
893
            return;
×
894
        }
895

896
        this._gestures = new IgxTouchManager(this.element.nativeElement, {
45✔
NEW
897
            tap: (event) => this.onTap(event),
×
NEW
898
            panMove: (event) => this.onPan(event),
×
NEW
899
            panEnd: (event) => this.onPanEnd(event)
×
900
        }, { tapThreshold: 5 });
901
    }
902

903
    /**
904
     * Routes a pan gesture to the orientation-specific handler so that only
905
     * gestures matching the carousel's axis affect the active slide.
906
     *
907
     * @hidden
908
     */
909
    private onPan(event) {
NEW
910
        if (Math.abs(event.deltaX) >= Math.abs(event.deltaY)) {
×
NEW
911
            if (event.deltaX < 0) {
×
NEW
912
                this.onPanLeft(event);
×
913
            } else {
NEW
914
                this.onPanRight(event);
×
915
            }
916
        } else {
NEW
917
            if (event.deltaY < 0) {
×
NEW
918
                this.onPanUp(event);
×
919
            } else {
NEW
920
                this.onPanDown(event);
×
921
            }
922
        }
923
    }
924

925
    private resetInterval() {
926
        if (this.lastInterval) {
143✔
927
            clearInterval(this.lastInterval);
70✔
928
            this.lastInterval = null;
70✔
929
        }
930
    }
931

932
    private restartInterval() {
933
        this.resetInterval();
133✔
934

935
        if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
133✔
936
            this.lastInterval = setInterval(() => {
97✔
937
                const tick = +this.interval;
2,088✔
938
                if (this.playing && this.total && !isNaN(tick) && tick > 0) {
2,088!
939
                    this.next();
2,088✔
940
                } else {
941
                    this.stop();
×
942
                }
943
            }, this.interval);
944
        }
945
    }
946

947
    /** @hidden */
948
    public get nextButtonDisabled() {
949
        return !this.loop && this.current === (this.total - 1);
806✔
950
    }
951

952
    /** @hidden */
953
    public get prevButtonDisabled() {
954
        return !this.loop && this.current === 0;
806✔
955
    }
956

957
    private get indicatorsElements() {
958
        return this._indicators.toArray();
16✔
959
    }
960

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

972
    private getNextIndex(): number {
973
        return (this.current + 1) % this.total;
2,115✔
974
    }
975

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

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

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

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

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

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

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

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

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

1037
    private unsubscriber(slide: IgxSlideComponent) {
1038
        return merge(this.destroy$, slide.isDestroyed);
190✔
1039
    }
1040

1041
    private onSlideActivated(slide: IgxSlideComponent) {
1042
        if (slide.active && slide !== this.currentItem) {
105✔
1043
            if (slide.direction === CarouselAnimationDirection.NONE) {
54✔
1044
                const newIndex = slide.index;
13✔
1045
                slide.direction = newIndex > this.current ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
13✔
1046
            }
1047

1048
            if (this.currentItem) {
54✔
1049
                if (this.previousItem && this.previousItem.previous) {
42!
1050
                    this.previousItem.previous = false;
×
1051
                }
1052
                this.currentItem.direction = slide.direction;
42✔
1053
                this.currentItem.active = false;
42✔
1054

1055
                this.previousItem = this.currentItem;
42✔
1056
                this.currentItem = slide;
42✔
1057
                this.triggerAnimations();
42✔
1058
            } else {
1059
                this.currentItem = slide;
12✔
1060
            }
1061
            this.slideChanged.emit({ carousel: this, slide });
54✔
1062
            this.restartInterval();
54✔
1063
            this.cdr.markForCheck();
54✔
1064
        }
1065
    }
1066

1067

1068
    private finishAnimations() {
1069
        if (this.animationStarted(this.leaveAnimationPlayer)) {
12!
1070
            this.leaveAnimationPlayer.finish();
×
1071
        }
1072

1073
        if (this.animationStarted(this.enterAnimationPlayer)) {
12!
1074
            this.enterAnimationPlayer.finish();
×
1075
        }
1076
    }
1077

1078
    private initSlides(change: QueryList<IgxSlideComponent>) {
1079
        const diff = this.differ.diff(change.toArray());
59✔
1080
        if (diff) {
59✔
1081
            this.slides.reduce((_any, c, ind) => c.index = ind, 0); // reset slides indexes
225✔
1082
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
59✔
1083
                const slide = record.item;
190✔
1084
                slide.total = this.total;
190✔
1085
                this.slideAdded.emit({ carousel: this, slide });
190✔
1086
                if (slide.active) {
190✔
1087
                    this.currentItem = slide;
18✔
1088
                }
1089
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
190✔
1090
            });
1091

1092
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
59✔
1093
                const slide = record.item;
14✔
1094
                this.slideRemoved.emit({ carousel: this, slide });
14✔
1095
                if (slide.active) {
14✔
1096
                    slide.active = false;
4✔
1097
                    if (this.currentItem === slide) { // Only fall back if nothing better was found.
4✔
1098
                        this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
4✔
1099
                    }
1100
                }
1101
            });
1102

1103
            this.updateSlidesSelection();
59✔
1104
        }
1105
    }
1106

1107
    private updateSlidesSelection() {
1108
        if (this.platformUtil.isBrowser) {
59✔
1109
            requestAnimationFrame(() => {
59✔
1110
                if (this.currentItem) {
59✔
1111
                    this.currentItem.active = true;
34✔
1112
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
133✔
1113
                    activeSlides.forEach(slide => slide.active = false);
34✔
1114
                } else if (this.total) {
25✔
1115
                    this.slides.first.active = true;
24✔
1116
                }
1117
                this.play();
59✔
1118
                this.cdr.markForCheck();
59✔
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 TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc