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

IgniteUI / igniteui-angular / 26023601418

18 May 2026 08:57AM UTC coverage: 4.854% (-85.3%) from 90.174%
26023601418

Pull #17281

github

web-flow
Merge e7ce7a18e into 5a85df190
Pull Request #17281: feat: Added virtual scroll component and sample implementation

400 of 17347 branches covered (2.31%)

Branch coverage included in aggregate %.

63 of 222 new or added lines in 4 files covered. (28.38%)

27932 existing lines in 341 files now uncovered.

2022 of 32547 relevant lines covered (6.21%)

0.72 hits per line

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

0.68
/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, Injectable, Input, IterableChangeRecord, IterableDiffer, IterableDiffers, OnDestroy, Output, QueryList, TemplateRef, ViewChild, ViewChildren, booleanAttribute, inject } from '@angular/core';
3
import { HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
4
import { merge, Subject } from 'rxjs';
5
import { takeUntil } from 'rxjs/operators';
6
import { CarouselResourceStringsEN, ICarouselResourceStrings, isLeftToRight} from 'igniteui-angular/core';
7
import { first, IBaseEventArgs, last, PlatformUtil } from 'igniteui-angular/core';
8
import { CarouselAnimationDirection, IgxCarouselComponentBase } from './carousel-base';
9
import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives';
10
import { IgxSlideComponent } from './slide.component';
11
import { IgxIconComponent } from 'igniteui-angular/icon';
12
import { IgxButtonDirective } from 'igniteui-angular/directives';
13
import { getCurrentResourceStrings, onResourceChangeHandle } from 'igniteui-angular/core';
14
import { HammerGesturesManager } from 'igniteui-angular/core';
15
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
16

17
let NEXT_ID = 0;
3✔
18

19

20
@Injectable()
21
export class CarouselHammerConfig extends HammerGestureConfig {
3✔
22
    public override overrides = {
×
23
        pan: { direction: HammerGesturesManager.Hammer?.DIRECTION_HORIZONTAL }
24
    };
25
}
26
/**
27
 * **Ignite UI for Angular Carousel** -
28
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel.html)
29
 *
30
 * The Ignite UI Carousel is used to browse or navigate through a collection of slides. Slides can contain custom
31
 * content such as images or cards and be used for things such as on-boarding tutorials or page-based interfaces.
32
 * It can be used as a separate fullscreen element or inside another component.
33
 *
34
 * Example:
35
 * ```html
36
 * <igx-carousel>
37
 *   <igx-slide>
38
 *     <h3>First Slide Header</h3>
39
 *     <p>First slide Content</p>
40
 *   <igx-slide>
41
 *   <igx-slide>
42
 *     <h3>Second Slide Header</h3>
43
 *     <p>Second Slide Content</p>
44
 * </igx-carousel>
45
 * ```
46
 */
47
@Component({
48
    providers: [
49
        {
50
            provide: HAMMER_GESTURE_CONFIG,
51
            useClass: CarouselHammerConfig
52
        }
53
    ],
54
    selector: 'igx-carousel',
55
    templateUrl: 'carousel.component.html',
56
    styles: [`
57
    :host {
58
        display: block;
59
        outline-style: none;
60
    }`],
61
    imports: [IgxButtonDirective, IgxIconComponent, NgClass, NgTemplateOutlet]
62
})
63
export class IgxCarouselComponent extends IgxCarouselComponentBase implements OnDestroy, AfterContentInit {
3✔
UNCOV
64
    private element = inject(ElementRef);
×
UNCOV
65
    private iterableDiffers = inject(IterableDiffers);
×
UNCOV
66
    private platformUtil = inject(PlatformUtil);
×
67

68

69

70
    /**
71
     * Sets the `id` of the carousel.
72
     * If not set, the `id` of the first carousel component will be `"igx-carousel-0"`.
73
     * ```html
74
     * <igx-carousel id="my-first-carousel"></igx-carousel>
75
     * ```
76
     *
77
     * @memberof IgxCarouselComponent
78
     */
79
    @HostBinding('attr.id')
80
    @Input()
UNCOV
81
    public id = `igx-carousel-${NEXT_ID++}`;
×
82
    /**
83
     * Returns the `role` attribute of the carousel.
84
     * ```typescript
85
     * let carouselRole =  this.carousel.role;
86
     * ```
87
     *
88
     * @memberof IgxCarouselComponent
89
     */
UNCOV
90
    @HostBinding('attr.role') public role = 'region';
×
91

92
    /** @hidden */
93
    @HostBinding('attr.aria-roledescription')
UNCOV
94
    public roleDescription = 'carousel';
×
95

96
    /** @hidden */
97
    @HostBinding('attr.aria-labelledby')
98
    public get labelId() {
UNCOV
99
        return this.showIndicatorsLabel ? `${this.id}-label` : null;
×
100
    }
101

102
    /** @hidden */
103
    @HostBinding('class.igx-carousel--vertical')
104
        public get isVertical(): boolean {
UNCOV
105
                return this.vertical;
×
106
        }
107

108
    /**
109
     * Returns the class of the carousel component.
110
     * ```typescript
111
     * let class =  this.carousel.cssClass;
112
     * ```
113
     *
114
     * @memberof IgxCarouselComponent
115
     */
116
    @HostBinding('class.igx-carousel')
UNCOV
117
    public cssClass = 'igx-carousel';
×
118

119
    /**
120
     * Gets the `touch-action` style of the `list item`.
121
     * ```typescript
122
     * let touchAction = this.listItem.touchAction;
123
     * ```
124
     */
125
    @HostBinding('style.touch-action')
126
    public get touchAction() {
UNCOV
127
        return this.gesturesSupport ? 'pan-y' : 'auto';
×
128
    }
129

130
    /**
131
     * Sets whether the carousel should `loop` back to the first slide after reaching the last slide.
132
     * Default value is `true`.
133
     * ```html
134
     * <igx-carousel [loop]="false"></igx-carousel>
135
     * ```
136
     *
137
     * @memberOf IgxCarouselComponent
138
     */
UNCOV
139
    @Input({ transform: booleanAttribute }) public loop = true;
×
140

141
    /**
142
     * Sets whether the carousel will `pause` the slide transitions on user interactions.
143
     * Default value is `true`.
144
     * ```html
145
     *  <igx-carousel [pause]="false"></igx-carousel>
146
     * ```
147
     *
148
     * @memberOf IgxCarouselComponent
149
     */
UNCOV
150
    @Input({ transform: booleanAttribute }) public pause = true;
×
151

152
    /**
153
     * Controls whether the carousel should render the left/right `navigation` buttons.
154
     * Default value is `true`.
155
     * ```html
156
     * <igx-carousel [navigation]="false"></igx-carousel>
157
     * ```
158
     *
159
     * @memberOf IgxCarouselComponent
160
     */
UNCOV
161
    @Input({ transform: booleanAttribute }) public navigation = true;
×
162

163
    /**
164
     * Controls whether the carousel should render the indicators.
165
     * Default value is `true`.
166
     * ```html
167
     * <igx-carousel [indicators]="false"></igx-carousel>
168
     * ```
169
     *
170
     * @memberOf IgxCarouselComponent
171
     */
UNCOV
172
    @Input({ transform: booleanAttribute }) public indicators = true;
×
173

174

175
    /**
176
     * Controls whether the carousel has vertical alignment.
177
     * Default value is `false`.
178
     * ```html
179
     * <igx-carousel [vertical]="true"></igx-carousel>
180
     * ```
181
     *
182
     * @memberOf IgxCarouselComponent
183
     */
UNCOV
184
    @Input({ transform: booleanAttribute }) public override vertical = false;
×
185

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

197
    /**
198
     * Controls the maximum indexes that can be shown.
199
     * Default value is `10`.
200
     * ```html
201
     * <igx-carousel [maximumIndicatorsCount]="5"></igx-carousel>
202
     * ```
203
     *
204
     * @memberOf IgxCarouselComponent
205
     */
UNCOV
206
    @Input() public maximumIndicatorsCount = 10;
×
207

208
    /**
209
     * Gets/sets the display mode of carousel indicators. It can be `start` or `end`.
210
     * Default value is `end`.
211
     * ```html
212
     * <igx-carousel indicatorsOrientation="start">
213
     * <igx-carousel>
214
     * ```
215
     *
216
     * @memberOf IgxCarouselComponent
217
     */
UNCOV
218
    @Input() public indicatorsOrientation: CarouselIndicatorsOrientation = CarouselIndicatorsOrientation.end;
×
219

220
    /**
221
     * Gets/sets the animation type of carousel.
222
     * Default value is `slide`.
223
     * ```html
224
     * <igx-carousel animationType="none">
225
     * <igx-carousel>
226
     * ```
227
     *
228
     * @memberOf IgxCarouselComponent
229
     */
UNCOV
230
    @Input() public override animationType: CarouselAnimationType = CarouselAnimationType.slide;
×
231

232
    /**
233
     * The custom template, if any, that should be used when rendering carousel indicators
234
     *
235
     * ```typescript
236
     * // Set in typescript
237
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
238
     * myComponent.carousel.indicatorTemplate = myCustomTemplate;
239
     * ```
240
     * ```html
241
     * <!-- Set in markup -->
242
     *  <igx-carousel #carousel>
243
     *      ...
244
     *      <ng-template igxCarouselIndicator let-slide>
245
     *         <igx-icon *ngIf="slide.active">brightness_7</igx-icon>
246
     *         <igx-icon *ngIf="!slide.active">brightness_5</igx-icon>
247
     *      </ng-template>
248
     *  </igx-carousel>
249
     * ```
250
     */
251
    @ContentChild(IgxCarouselIndicatorDirective, { read: TemplateRef, static: false })
UNCOV
252
    public indicatorTemplate: TemplateRef<any> = null;
×
253

254
    /**
255
     * The custom template, if any, that should be used when rendering carousel next button
256
     *
257
     * ```typescript
258
     * // Set in typescript
259
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
260
     * myComponent.carousel.nextButtonTemplate = myCustomTemplate;
261
     * ```
262
     * ```html
263
     * <!-- Set in markup -->
264
     *  <igx-carousel #carousel>
265
     *      ...
266
     *      <ng-template igxCarouselNextButton let-disabled>
267
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
268
     *              <igx-icon name="add"></igx-icon>
269
     *          </button>
270
     *      </ng-template>
271
     *  </igx-carousel>
272
     * ```
273
     */
274
    @ContentChild(IgxCarouselNextButtonDirective, { read: TemplateRef, static: false })
UNCOV
275
    public nextButtonTemplate: TemplateRef<any> = null;
×
276

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

300
    /**
301
     * The collection of `slides` currently in the carousel.
302
     * ```typescript
303
     * let slides: QueryList<IgxSlideComponent> = this.carousel.slides;
304
     * ```
305
     *
306
     * @memberOf IgxCarouselComponent
307
     */
308
    @ContentChildren(IgxSlideComponent)
309
    public slides: QueryList<IgxSlideComponent>;
310

311
    /**
312
     * An event that is emitted after a slide transition has happened.
313
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
314
     * ```html
315
     * <igx-carousel (slideChanged)="slideChanged($event)"></igx-carousel>
316
     * ```
317
     *
318
     * @memberOf IgxCarouselComponent
319
     */
UNCOV
320
    @Output() public slideChanged = new EventEmitter<ISlideEventArgs>();
×
321

322
    /**
323
     * An event that is emitted after a slide has been added to the carousel.
324
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
325
     * ```html
326
     * <igx-carousel (slideAdded)="slideAdded($event)"></igx-carousel>
327
     * ```
328
     *
329
     * @memberOf IgxCarouselComponent
330
     */
UNCOV
331
    @Output() public slideAdded = new EventEmitter<ISlideEventArgs>();
×
332

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

344
    /**
345
     * An event that is emitted after the carousel has been paused.
346
     * Provides a reference to the `IgxCarouselComponent` as an event argument.
347
     * ```html
348
     * <igx-carousel (carouselPaused)="carouselPaused($event)"></igx-carousel>
349
     * ```
350
     *
351
     * @memberOf IgxCarouselComponent
352
     */
UNCOV
353
    @Output() public carouselPaused = new EventEmitter<IgxCarouselComponent>();
×
354

355
    /**
356
     * An event that is emitted after the carousel has resumed transitioning between `slides`.
357
     * Provides a reference to the `IgxCarouselComponent` as an event argument.
358
     * ```html
359
     * <igx-carousel (carouselPlaying)="carouselPlaying($event)"></igx-carousel>
360
     * ```
361
     *
362
     * @memberOf IgxCarouselComponent
363
     */
UNCOV
364
    @Output() public carouselPlaying = new EventEmitter<IgxCarouselComponent>();
×
365

366
    @ViewChild('defaultIndicator', { read: TemplateRef, static: true })
367
    private defaultIndicator: TemplateRef<any>;
368

369
    @ViewChild('defaultNextButton', { read: TemplateRef, static: true })
370
    private defaultNextButton: TemplateRef<any>;
371

372
    @ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
373
    private defaultPrevButton: TemplateRef<any>;
374

375
    @ViewChildren('indicators', { read: ElementRef })
376
    private _indicators: QueryList<ElementRef<HTMLDivElement>>;
377

378
    /**
379
     * @hidden
380
     * @internal
381
     */
382
    public stoppedByInteraction: boolean;
383
    protected override currentItem: IgxSlideComponent;
384
    protected override previousItem: IgxSlideComponent;
385
    private _interval: number;
UNCOV
386
    private _resourceStrings: ICarouselResourceStrings = null;
×
UNCOV
387
    private _defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN);
×
388
    private lastInterval: any;
389
    private playing: boolean;
390
    private destroyed: boolean;
UNCOV
391
    private destroy$ = new Subject<any>();
×
UNCOV
392
    private differ: IterableDiffer<IgxSlideComponent> | null = null;
×
393
    private incomingSlide: IgxSlideComponent;
UNCOV
394
    private _hasKeyboardFocusOnIndicators = false;
×
395

396
    /**
397
     * An accessor that sets the resource strings.
398
     * By default it uses EN resources.
399
     */
400
    @Input()
401
    public set resourceStrings(value: ICarouselResourceStrings) {
402
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
403
    }
404

405
    /**
406
     * An accessor that returns the resource strings.
407
     */
408
    public get resourceStrings(): ICarouselResourceStrings {
UNCOV
409
        return this._resourceStrings || this._defaultResourceStrings;
×
410
    }
411

412
    /** @hidden */
413
    public get getIndicatorTemplate(): TemplateRef<any> {
UNCOV
414
        if (this.indicatorTemplate) {
×
UNCOV
415
            return this.indicatorTemplate;
×
416
        }
UNCOV
417
        return this.defaultIndicator;
×
418
    }
419

420
    /** @hidden */
421
    public get getNextButtonTemplate(): TemplateRef<any> {
UNCOV
422
        if (this.nextButtonTemplate) {
×
UNCOV
423
            return this.nextButtonTemplate;
×
424
        }
425

UNCOV
426
        return this.defaultNextButton
×
427
    }
428

429
    /** @hidden */
430
    public get getPrevButtonTemplate(): TemplateRef<any> {
UNCOV
431
        if (this.prevButtonTemplate) {
×
UNCOV
432
            return this.prevButtonTemplate;
×
433
        }
434

UNCOV
435
        return this.defaultPrevButton
×
436
    }
437

438
    /** @hidden */
439
    public get indicatorsClass() {
UNCOV
440
        return {
×
441
            ['igx-carousel-indicators--focused']: this._hasKeyboardFocusOnIndicators,
442
            [`igx-carousel-indicators--${this.getIndicatorsClass()}`]: true
443
        };
444
    }
445

446
    /** @hidden */
447
    public get showIndicators(): boolean {
UNCOV
448
        return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
×
449
    }
450

451
    /** @hidden */
452
    public get showIndicatorsLabel(): boolean {
UNCOV
453
        return this.indicators && this.total > this.maximumIndicatorsCount;
×
454
    }
455

456
    /** @hidden */
457
    public get getCarouselLabel() {
UNCOV
458
        return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
×
459
    }
460

461
    /**
462
     * Returns the total number of `slides` in the carousel.
463
     * ```typescript
464
     * let slideCount =  this.carousel.total;
465
     * ```
466
     *
467
     * @memberOf IgxCarouselComponent
468
     */
469
    public get total(): number {
UNCOV
470
        return this.slides?.length;
×
471
    }
472

473
    /**
474
     * The index of the slide being currently shown.
475
     * ```typescript
476
     * let currentSlideNumber =  this.carousel.current;
477
     * ```
478
     *
479
     * @memberOf IgxCarouselComponent
480
     */
481
    public get current(): number {
UNCOV
482
        return !this.currentItem ? 0 : this.currentItem.index;
×
483
    }
484

485
    /**
486
     * Returns a boolean indicating if the carousel is playing.
487
     * ```typescript
488
     * let isPlaying =  this.carousel.isPlaying;
489
     * ```
490
     *
491
     * @memberOf IgxCarouselComponent
492
     */
493
    public get isPlaying(): boolean {
UNCOV
494
        return this.playing;
×
495
    }
496

497
    /**
498
     * Returns а boolean indicating if the carousel is destroyed.
499
     * ```typescript
500
     * let isDestroyed =  this.carousel.isDestroyed;
501
     * ```
502
     *
503
     * @memberOf IgxCarouselComponent
504
     */
505
    public get isDestroyed(): boolean {
UNCOV
506
        return this.destroyed;
×
507
    }
508
    /**
509
     * Returns a reference to the carousel element in the DOM.
510
     * ```typescript
511
     * let nativeElement =  this.carousel.nativeElement;
512
     * ```
513
     *
514
     * @memberof IgxCarouselComponent
515
     */
516
    public get nativeElement(): any {
UNCOV
517
        return this.element.nativeElement;
×
518
    }
519

520
    /**
521
     * Returns the time `interval` in milliseconds before the slide changes.
522
     * ```typescript
523
     * let timeInterval = this.carousel.interval;
524
     * ```
525
     *
526
     * @memberof IgxCarouselComponent
527
     */
528
    @Input()
529
    public get interval(): number {
UNCOV
530
        return this._interval;
×
531
    }
532

533
    /**
534
     * Sets the time `interval` in milliseconds before the slide changes.
535
     * If not set, the carousel will not change `slides` automatically.
536
     * ```html
537
     * <igx-carousel [interval]="1000"></igx-carousel>
538
     * ```
539
     *
540
     * @memberof IgxCarouselComponent
541
     */
542
    public set interval(value: number) {
UNCOV
543
        this._interval = +value;
×
UNCOV
544
        this.restartInterval();
×
545
    }
546

547
    constructor() {
UNCOV
548
        super();
×
UNCOV
549
        this.differ = this.iterableDiffers.find([]).create(null);
×
UNCOV
550
        onResourceChangeHandle(this.destroy$, () => {
×
551
            this._defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN, false);
×
552
        }, this);
553
    }
554

555
    /** @hidden */
556
    @HostListener('tap', ['$event'])
557
    public onTap(event) {
558
        // play pause only when tap on slide
UNCOV
559
        if (event.target && event.target.classList.contains('igx-slide')) {
×
UNCOV
560
            if (this.isPlaying) {
×
UNCOV
561
                if (this.pause) {
×
UNCOV
562
                    this.stoppedByInteraction = true;
×
563
                }
UNCOV
564
                this.stop();
×
UNCOV
565
            } else if (this.stoppedByInteraction) {
×
UNCOV
566
                this.play();
×
567
            }
568
        }
569
    }
570

571
    /** @hidden */
572
    @HostListener('mouseenter')
573
    public onMouseEnter() {
UNCOV
574
        if (this.pause && this.isPlaying) {
×
UNCOV
575
            this.stoppedByInteraction = true;
×
576
        }
UNCOV
577
        this.stop();
×
578
    }
579

580
    /** @hidden */
581
    @HostListener('mouseleave')
582
    public onMouseLeave() {
UNCOV
583
        if (this.stoppedByInteraction) {
×
UNCOV
584
            this.play();
×
585
        }
586
    }
587

588
    /** @hidden */
589
    @HostListener('panleft', ['$event'])
590
    public onPanLeft(event) {
UNCOV
591
        if (!this.vertical) {
×
UNCOV
592
            this.pan(event);
×
593
        }
594
    }
595

596
    /** @hidden */
597
    @HostListener('panright', ['$event'])
598
    public onPanRight(event) {
UNCOV
599
        if (!this.vertical) {
×
UNCOV
600
            this.pan(event);
×
601
        }
602
    }
603

604
    /** @hidden */
605
    @HostListener('panup', ['$event'])
606
    public onPanUp(event) {
UNCOV
607
        if (this.vertical) {
×
UNCOV
608
            this.pan(event);
×
609
        }
610
    }
611

612
    /** @hidden */
613
    @HostListener('pandown', ['$event'])
614
    public onPanDown(event) {
UNCOV
615
        if (this.vertical) {
×
UNCOV
616
            this.pan(event);
×
617
        }
618
    }
619

620
    /**
621
     * @hidden
622
     */
623
    @HostListener('panend', ['$event'])
624
    public onPanEnd(event) {
UNCOV
625
        if (!this.gesturesSupport) {
×
UNCOV
626
            return;
×
627
        }
UNCOV
628
        event.preventDefault();
×
629

UNCOV
630
        const slideSize = this.vertical
×
631
            ? this.currentItem.nativeElement.offsetHeight
632
            : this.currentItem.nativeElement.offsetWidth;
UNCOV
633
        const panOffset = (slideSize / 1000);
×
UNCOV
634
        const eventDelta = this.vertical ? event.deltaY : event.deltaX;
×
UNCOV
635
        const delta = Math.abs(eventDelta) + panOffset < slideSize ? Math.abs(eventDelta) : slideSize - panOffset;
×
UNCOV
636
        const velocity = Math.abs(event.velocity);
×
UNCOV
637
        this.resetSlideStyles(this.currentItem);
×
UNCOV
638
        if (this.incomingSlide) {
×
UNCOV
639
            this.resetSlideStyles(this.incomingSlide);
×
UNCOV
640
            if (slideSize / 2 < delta || velocity > 1) {
×
UNCOV
641
                this.incomingSlide.direction = eventDelta < 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
×
UNCOV
642
                this.incomingSlide.previous = false;
×
643

UNCOV
644
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
×
645
                    delta / slideSize : (slideSize - delta) / slideSize;
646

UNCOV
647
                if (velocity > 1) {
×
UNCOV
648
                    this.newDuration = this.defaultAnimationDuration / velocity;
×
649
                }
UNCOV
650
                this.incomingSlide.active = true;
×
651
            } else {
UNCOV
652
                this.currentItem.direction = eventDelta > 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
×
UNCOV
653
                this.previousItem = this.incomingSlide;
×
UNCOV
654
                this.previousItem.previous = true;
×
UNCOV
655
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
×
656
                    Math.abs((slideSize - delta) / slideSize) : delta / slideSize;
UNCOV
657
                this.playAnimations();
×
658
            }
659
        }
660

UNCOV
661
        if (this.stoppedByInteraction) {
×
662
            this.play();
×
663
        }
664
    }
665

666
    /** @hidden */
667
    public ngAfterContentInit() {
UNCOV
668
        this.slides.changes
×
669
            .pipe(takeUntil(this.destroy$))
UNCOV
670
            .subscribe((change: QueryList<IgxSlideComponent>) => this.initSlides(change));
×
671

UNCOV
672
        this.initSlides(this.slides);
×
673
    }
674

675
    /** @hidden */
676
    public override ngOnDestroy() {
UNCOV
677
        super.ngOnDestroy();
×
UNCOV
678
        this.destroy$.next(true);
×
UNCOV
679
        this.destroy$.complete();
×
UNCOV
680
        this.destroyed = true;
×
UNCOV
681
        if (this.lastInterval) {
×
UNCOV
682
            clearInterval(this.lastInterval);
×
683
        }
684
    }
685

686
    /** @hidden */
687
    public handleKeydownPrev(event: KeyboardEvent): void {
UNCOV
688
        if (this.platformUtil.isActivationKey(event)) {
×
UNCOV
689
            event.preventDefault();
×
UNCOV
690
            this.prev();
×
691
        }
692
    }
693

694
    /** @hidden */
695
    public handleKeydownNext(event: KeyboardEvent): void {
UNCOV
696
        if (this.platformUtil.isActivationKey(event)) {
×
UNCOV
697
            event.preventDefault();
×
UNCOV
698
            this.next();
×
699
        }
700
    }
701

702
    /** @hidden */
703
    public handleKeyUp(event: KeyboardEvent): void {
UNCOV
704
        if (event.key === this.platformUtil.KEYMAP.TAB) {
×
UNCOV
705
            this._hasKeyboardFocusOnIndicators = true;
×
706
        }
707
    }
708

709
    /** @hidden */
710
    public handleFocusOut(event: FocusEvent): void {
UNCOV
711
        const target = event.relatedTarget as HTMLElement;
×
712

UNCOV
713
        if (!target || !target.classList.contains('igx-carousel-indicators__indicator')) {
×
UNCOV
714
            this._hasKeyboardFocusOnIndicators = false;
×
715
        }
716
    }
717

718
    /** @hidden */
719
    public handleClick(): void {
UNCOV
720
        this._hasKeyboardFocusOnIndicators = false;
×
721
    }
722

723
    /** @hidden */
724
    public handleKeydown(event: KeyboardEvent): void {
UNCOV
725
        const { key } = event;
×
UNCOV
726
        const slides = this.slides.toArray();
×
UNCOV
727
        const isRTL = !isLeftToRight(this.nativeElement);
×
728

UNCOV
729
        switch (key) {
×
730
            case this.platformUtil.KEYMAP.ARROW_LEFT:
UNCOV
731
                isRTL ? this.next() : this.prev();
×
UNCOV
732
                break;
×
733
            case this.platformUtil.KEYMAP.ARROW_RIGHT:
UNCOV
734
                isRTL ? this.prev() : this.next();
×
UNCOV
735
                break;
×
736
            case this.platformUtil.KEYMAP.HOME:
UNCOV
737
                event.preventDefault();
×
UNCOV
738
                this.select(isRTL ? last(slides) : first(slides));
×
UNCOV
739
                break;
×
740
            case this.platformUtil.KEYMAP.END:
UNCOV
741
                event.preventDefault();
×
UNCOV
742
                this.select(isRTL ? first(slides) : last(slides));
×
UNCOV
743
                break;
×
744
        }
745

UNCOV
746
        this.indicatorsElements[this.current].nativeElement.focus();
×
747
    }
748

749
    /**
750
     * Returns the slide corresponding to the provided `index` or null.
751
     * ```typescript
752
     * let slide1 =  this.carousel.get(1);
753
     * ```
754
     *
755
     * @memberOf IgxCarouselComponent
756
     */
757
    public get(index: number): IgxSlideComponent {
UNCOV
758
        return this.slides.find((slide) => slide.index === index);
×
759
    }
760

761
    /**
762
     * Adds a new slide to the carousel.
763
     * ```typescript
764
     * this.carousel.add(newSlide);
765
     * ```
766
     *
767
     * @memberOf IgxCarouselComponent
768
     */
769
    public add(slide: IgxSlideComponent) {
UNCOV
770
        const newSlides = this.slides.toArray();
×
UNCOV
771
        newSlides.push(slide);
×
UNCOV
772
        this.slides.reset(newSlides);
×
UNCOV
773
        this.slides.notifyOnChanges();
×
774
    }
775

776
    /**
777
     * Removes a slide from the carousel.
778
     * ```typescript
779
     * this.carousel.remove(slide);
780
     * ```
781
     *
782
     * @memberOf IgxCarouselComponent
783
     */
784
    public remove(slide: IgxSlideComponent) {
UNCOV
785
        if (slide && slide === this.get(slide.index)) { // check if the requested slide for delete is present in the carousel
×
UNCOV
786
            const newSlides = this.slides.toArray();
×
UNCOV
787
            newSlides.splice(slide.index, 1);
×
UNCOV
788
            this.slides.reset(newSlides);
×
UNCOV
789
            this.slides.notifyOnChanges();
×
790
        }
791
    }
792

793
    /**
794
     * Switches to the passed-in slide with a given `direction`.
795
     * ```typescript
796
     * const slide = this.carousel.get(2);
797
     * this.carousel.select(slide, CarouselAnimationDirection.NEXT);
798
     * ```
799
     *
800
     * @memberOf IgxCarouselComponent
801
     */
802
    public select(slide: IgxSlideComponent, direction?: CarouselAnimationDirection): void;
803
    /**
804
     * Switches to slide by index with a given `direction`.
805
     * ```typescript
806
     * this.carousel.select(2, CarouselAnimationDirection.NEXT);
807
     * ```
808
     *
809
     * @memberOf IgxCarouselComponent
810
     */
811
    public select(index: number, direction?: CarouselAnimationDirection): void;
812
    public select(slideOrIndex: IgxSlideComponent | number, direction: CarouselAnimationDirection = CarouselAnimationDirection.NONE): void {
×
UNCOV
813
        const slide = typeof slideOrIndex === 'number'
×
814
            ? this.get(slideOrIndex)
815
            : slideOrIndex;
816

UNCOV
817
        if (slide && slide !== this.currentItem) {
×
UNCOV
818
            slide.direction = direction;
×
UNCOV
819
            slide.active = true;
×
820
        }
821
    }
822

823
    /**
824
     * Transitions to the next slide in the carousel.
825
     * ```typescript
826
     * this.carousel.next();
827
     * ```
828
     *
829
     * @memberOf IgxCarouselComponent
830
     */
831
    public next() {
UNCOV
832
        const index = this.getNextIndex();
×
833

UNCOV
834
        if (index === 0 && !this.loop) {
×
UNCOV
835
            this.stop();
×
UNCOV
836
            return;
×
837
        }
UNCOV
838
        return this.select(this.get(index), CarouselAnimationDirection.NEXT);
×
839
    }
840

841
    /**
842
     * Transitions to the previous slide in the carousel.
843
     * ```typescript
844
     * this.carousel.prev();
845
     * ```
846
     *
847
     * @memberOf IgxCarouselComponent
848
     */
849
    public prev() {
UNCOV
850
        const index = this.getPrevIndex();
×
851

UNCOV
852
        if (!this.loop && index === this.total - 1) {
×
UNCOV
853
            this.stop();
×
UNCOV
854
            return;
×
855
        }
UNCOV
856
        return this.select(this.get(index), CarouselAnimationDirection.PREV);
×
857
    }
858

859
    /**
860
     * Resumes playing of the carousel if in paused state.
861
     * No operation otherwise.
862
     * ```typescript
863
     * this.carousel.play();
864
     * }
865
     * ```
866
     *
867
     * @memberOf IgxCarouselComponent
868
     */
869
    public play() {
UNCOV
870
        if (!this.playing) {
×
UNCOV
871
            this.playing = true;
×
UNCOV
872
            this.carouselPlaying.emit(this);
×
UNCOV
873
            this.restartInterval();
×
UNCOV
874
            this.stoppedByInteraction = false;
×
875
        }
876
    }
877

878
    /**
879
     * Stops slide transitions if the `pause` option is set to `true`.
880
     * No operation otherwise.
881
     * ```typescript
882
     *  this.carousel.stop();
883
     * }
884
     * ```
885
     *
886
     * @memberOf IgxCarouselComponent
887
     */
888
    public stop() {
UNCOV
889
        if (this.pause) {
×
UNCOV
890
            this.playing = false;
×
UNCOV
891
            this.carouselPaused.emit(this);
×
UNCOV
892
            this.resetInterval();
×
893
        }
894
    }
895

896
    protected getPreviousElement(): HTMLElement {
UNCOV
897
        return this.previousItem.nativeElement;
×
898
    }
899

900
    protected getCurrentElement(): HTMLElement {
UNCOV
901
        return this.currentItem.nativeElement;
×
902
    }
903

904
    private resetInterval() {
UNCOV
905
        if (this.lastInterval) {
×
UNCOV
906
            clearInterval(this.lastInterval);
×
UNCOV
907
            this.lastInterval = null;
×
908
        }
909
    }
910

911
    private restartInterval() {
UNCOV
912
        this.resetInterval();
×
913

UNCOV
914
        if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
×
UNCOV
915
            this.lastInterval = setInterval(() => {
×
UNCOV
916
                const tick = +this.interval;
×
UNCOV
917
                if (this.playing && this.total && !isNaN(tick) && tick > 0) {
×
UNCOV
918
                    this.next();
×
919
                } else {
920
                    this.stop();
×
921
                }
922
            }, this.interval);
923
        }
924
    }
925

926
    /** @hidden */
927
    public get nextButtonDisabled() {
UNCOV
928
        return !this.loop && this.current === (this.total - 1);
×
929
    }
930

931
    /** @hidden */
932
    public get prevButtonDisabled() {
UNCOV
933
        return !this.loop && this.current === 0;
×
934
    }
935

936
    private get indicatorsElements() {
UNCOV
937
        return this._indicators.toArray();
×
938
    }
939

940
    private getIndicatorsClass(): string {
UNCOV
941
        switch (this.indicatorsOrientation) {
×
942
            case CarouselIndicatorsOrientation.top:
943
                return CarouselIndicatorsOrientation.start;
×
944
            case CarouselIndicatorsOrientation.bottom:
945
                return CarouselIndicatorsOrientation.end;
×
946
            default:
UNCOV
947
                return this.indicatorsOrientation;
×
948
        }
949
    }
950

951
    private getNextIndex(): number {
UNCOV
952
        return (this.current + 1) % this.total;
×
953
    }
954

955
    private getPrevIndex(): number {
UNCOV
956
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
×
957
    }
958

959
    private resetSlideStyles(slide: IgxSlideComponent) {
UNCOV
960
        slide.nativeElement.style.transform = '';
×
UNCOV
961
        slide.nativeElement.style.opacity = '';
×
962
    }
963

964
    private pan(event) {
UNCOV
965
        const slideSize = this.vertical
×
966
            ? this.currentItem.nativeElement.offsetHeight
967
            : this.currentItem.nativeElement.offsetWidth;
UNCOV
968
        const panOffset = (slideSize / 1000);
×
UNCOV
969
        const delta = this.vertical ? event.deltaY : event.deltaX;
×
UNCOV
970
        const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
×
UNCOV
971
        const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
×
972

UNCOV
973
        if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
×
UNCOV
974
            return;
×
975
        }
976

UNCOV
977
        if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
×
UNCOV
978
            this.incomingSlide = null;
×
UNCOV
979
            return;
×
980
        }
981

UNCOV
982
        event.preventDefault();
×
UNCOV
983
        if (this.isPlaying) {
×
984
            this.stoppedByInteraction = true;
×
985
            this.stop();
×
986
        }
987

UNCOV
988
        if (this.previousItem && this.previousItem.previous) {
×
UNCOV
989
            this.previousItem.previous = false;
×
990
        }
UNCOV
991
        this.finishAnimations();
×
992

UNCOV
993
        if (this.incomingSlide) {
×
UNCOV
994
            if (index !== this.incomingSlide.index) {
×
UNCOV
995
                this.resetSlideStyles(this.incomingSlide);
×
UNCOV
996
                this.incomingSlide.previous = false;
×
UNCOV
997
                this.incomingSlide = this.get(index);
×
998
            }
999
        } else {
UNCOV
1000
            this.incomingSlide = this.get(index);
×
1001
        }
UNCOV
1002
        this.incomingSlide.previous = true;
×
1003

UNCOV
1004
        if (this.animationType === CarouselAnimationType.fade) {
×
1005
            this.currentItem.nativeElement.style.opacity = `${Math.abs(offset) / slideSize}`;
×
1006
        } else {
UNCOV
1007
            this.currentItem.nativeElement.style.transform = this.vertical
×
1008
                ? `translateY(${delta}px)`
1009
                : `translateX(${delta}px)`;
UNCOV
1010
            this.incomingSlide.nativeElement.style.transform = this.vertical
×
1011
                ? `translateY(${offset}px)`
1012
                : `translateX(${offset}px)`;
1013
        }
1014
    }
1015

1016
    private unsubscriber(slide: IgxSlideComponent) {
UNCOV
1017
        return merge(this.destroy$, slide.isDestroyed);
×
1018
    }
1019

1020
    private onSlideActivated(slide: IgxSlideComponent) {
UNCOV
1021
        if (slide.active && slide !== this.currentItem) {
×
UNCOV
1022
            if (slide.direction === CarouselAnimationDirection.NONE) {
×
UNCOV
1023
                const newIndex = slide.index;
×
UNCOV
1024
                slide.direction = newIndex > this.current ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
×
1025
            }
1026

UNCOV
1027
            if (this.currentItem) {
×
UNCOV
1028
                if (this.previousItem && this.previousItem.previous) {
×
1029
                    this.previousItem.previous = false;
×
1030
                }
UNCOV
1031
                this.currentItem.direction = slide.direction;
×
UNCOV
1032
                this.currentItem.active = false;
×
1033

UNCOV
1034
                this.previousItem = this.currentItem;
×
UNCOV
1035
                this.currentItem = slide;
×
UNCOV
1036
                this.triggerAnimations();
×
1037
            } else {
UNCOV
1038
                this.currentItem = slide;
×
1039
            }
UNCOV
1040
            this.slideChanged.emit({ carousel: this, slide });
×
UNCOV
1041
            this.restartInterval();
×
UNCOV
1042
            this.cdr.markForCheck();
×
1043
        }
1044
    }
1045

1046

1047
    private finishAnimations() {
UNCOV
1048
        if (this.animationStarted(this.leaveAnimationPlayer)) {
×
1049
            this.leaveAnimationPlayer.finish();
×
1050
        }
1051

UNCOV
1052
        if (this.animationStarted(this.enterAnimationPlayer)) {
×
1053
            this.enterAnimationPlayer.finish();
×
1054
        }
1055
    }
1056

1057
    private initSlides(change: QueryList<IgxSlideComponent>) {
UNCOV
1058
        const diff = this.differ.diff(change.toArray());
×
UNCOV
1059
        if (diff) {
×
UNCOV
1060
            this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
×
UNCOV
1061
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
×
UNCOV
1062
                const slide = record.item;
×
UNCOV
1063
                slide.total = this.total;
×
UNCOV
1064
                this.slideAdded.emit({ carousel: this, slide });
×
UNCOV
1065
                if (slide.active) {
×
UNCOV
1066
                    this.currentItem = slide;
×
1067
                }
UNCOV
1068
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
×
1069
            });
1070

UNCOV
1071
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
×
UNCOV
1072
                const slide = record.item;
×
UNCOV
1073
                this.slideRemoved.emit({ carousel: this, slide });
×
UNCOV
1074
                if (slide.active) {
×
UNCOV
1075
                    slide.active = false;
×
UNCOV
1076
                    this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
×
1077
                }
1078
            });
1079

UNCOV
1080
            this.updateSlidesSelection();
×
1081
        }
1082
    }
1083

1084
    private updateSlidesSelection() {
UNCOV
1085
        if (this.platformUtil.isBrowser) {
×
UNCOV
1086
            requestAnimationFrame(() => {
×
UNCOV
1087
                if (this.currentItem) {
×
UNCOV
1088
                    this.currentItem.active = true;
×
UNCOV
1089
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
×
UNCOV
1090
                    activeSlides.forEach(slide => slide.active = false);
×
UNCOV
1091
                } else if (this.total) {
×
UNCOV
1092
                    this.slides.first.active = true;
×
1093
                }
UNCOV
1094
                this.play();
×
1095
            });
1096
        }
1097
    }
1098
}
1099

1100
export interface ISlideEventArgs extends IBaseEventArgs {
1101
    carousel: IgxCarouselComponent;
1102
    slide: IgxSlideComponent;
1103
}
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