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

IgniteUI / igniteui-angular / 13548823408

26 Feb 2025 04:36PM CUT coverage: 91.555% (-0.04%) from 91.599%
13548823408

Pull #15223

github

web-flow
Merge 3ac8087aa into 786685675
Pull Request #15223: fix(pivot-grid): added createRow method for grid based events - 19.0

12997 of 15250 branches covered (85.23%)

0 of 18 new or added lines in 2 files covered. (0.0%)

135 existing lines in 23 files now uncovered.

26344 of 28774 relevant lines covered (91.55%)

34046.37 hits per line

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

95.22
/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
    imports: [IgxButtonDirective, IgxIconComponent, NgIf, NgClass, NgFor, NgTemplateOutlet]
89
})
90

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

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

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

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

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

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

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

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

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

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

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

197

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

460
        return this.defaultNextButton
394✔
461
    }
462

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

469
        return this.defaultPrevButton
394✔
470
    }
471

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

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

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

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

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

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

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

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

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

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

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

594

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1110

1111
    private finishAnimations() {
1112
        if (this.animationStarted(this.leaveAnimationPlayer)) {
12!
UNCOV
1113
            this.leaveAnimationPlayer.finish();
×
1114
        }
1115

1116
        if (this.animationStarted(this.enterAnimationPlayer)) {
12!
UNCOV
1117
            this.enterAnimationPlayer.finish();
×
1118
        }
1119
    }
1120

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

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

1144
            this.updateSlidesSelection();
56✔
1145
        }
1146
    }
1147

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

1174
}
1175

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

© 2025 Coveralls, Inc