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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

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

1.01
/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()
UNCOV
104
    public id = `igx-carousel-${NEXT_ID++}`;
×
105
    /**
106
     * Returns the `role` attribute of the carousel.
107
     * ```typescript
108
     * let carouselRole =  this.carousel.role;
109
     * ```
110
     *
111
     * @memberof IgxCarouselComponent
112
     */
UNCOV
113
    @HostBinding('attr.role') public role = 'region';
×
114

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

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

125
    /** @hidden */
126
    @HostBinding('class.igx-carousel--vertical')
127
        public get isVertical(): boolean {
UNCOV
128
                return this.vertical;
×
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')
UNCOV
140
    public cssClass = 'igx-carousel';
×
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() {
UNCOV
150
        return this.gesturesSupport ? 'pan-y' : 'auto';
×
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
     */
UNCOV
162
    @Input({ transform: booleanAttribute }) public loop = true;
×
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
     */
UNCOV
173
    @Input({ transform: booleanAttribute }) public pause = true;
×
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
     */
UNCOV
184
    @Input({ transform: booleanAttribute }) public navigation = true;
×
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
     */
UNCOV
195
    @Input({ transform: booleanAttribute }) public indicators = true;
×
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
     */
UNCOV
207
    @Input({ transform: booleanAttribute }) public override vertical = false;
×
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
     */
UNCOV
219
    @Input({ transform: booleanAttribute }) public keyboardSupport = false;
×
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
     */
UNCOV
230
    @Input({ transform: booleanAttribute }) public gesturesSupport = true;
×
231

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

243
    /**
244
     * Gets/sets the display mode of carousel indicators. It can be `start` or `end`.
245
     * Default value is `end`.
246
     * ```html
247
     * <igx-carousel indicatorsOrientation="start">
248
     * <igx-carousel>
249
     * ```
250
     *
251
     * @memberOf IgxCarouselComponent
252
     */
UNCOV
253
    @Input() public indicatorsOrientation: CarouselIndicatorsOrientation = CarouselIndicatorsOrientation.end;
×
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 IgxCarouselComponent
264
     */
UNCOV
265
    @Input() public override animationType: CarouselAnimationType = CarouselAnimationType.slide;
×
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 })
UNCOV
287
    public indicatorTemplate: TemplateRef<any> = null;
×
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 })
UNCOV
310
    public nextButtonTemplate: TemplateRef<any> = null;
×
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 })
UNCOV
333
    public prevButtonTemplate: TemplateRef<any> = null;
×
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
     */
UNCOV
355
    @Output() public slideChanged = new EventEmitter<ISlideEventArgs>();
×
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
     */
UNCOV
366
    @Output() public slideAdded = new EventEmitter<ISlideEventArgs>();
×
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
     */
UNCOV
377
    @Output() public slideRemoved = new EventEmitter<ISlideEventArgs>();
×
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
     */
UNCOV
388
    @Output() public carouselPaused = new EventEmitter<IgxCarouselComponent>();
×
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
     */
UNCOV
399
    @Output() public carouselPlaying = new EventEmitter<IgxCarouselComponent>();
×
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;
UNCOV
421
    private _resourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN);
×
422
    private lastInterval: any;
423
    private playing: boolean;
424
    private destroyed: boolean;
UNCOV
425
    private destroy$ = new Subject<any>();
×
UNCOV
426
    private differ: IterableDiffer<IgxSlideComponent> | null = null;
×
427
    private incomingSlide: IgxSlideComponent;
UNCOV
428
    private _hasKeyboardFocusOnIndicators = false;
×
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) {
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 {
UNCOV
443
        return this._resourceStrings;
×
444
    }
445

446
    /** @hidden */
447
    public get getIndicatorTemplate(): TemplateRef<any> {
UNCOV
448
        if (this.indicatorTemplate) {
×
UNCOV
449
            return this.indicatorTemplate;
×
450
        }
UNCOV
451
        return this.defaultIndicator;
×
452
    }
453

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

UNCOV
460
        return this.defaultNextButton
×
461
    }
462

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

UNCOV
469
        return this.defaultPrevButton
×
470
    }
471

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

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

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

490
    /** @hidden */
491
    public get getCarouselLabel() {
UNCOV
492
        return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
×
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 {
UNCOV
504
        return this.slides?.length;
×
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 {
UNCOV
516
        return !this.currentItem ? 0 : this.currentItem.index;
×
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 {
UNCOV
528
        return this.playing;
×
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 {
UNCOV
540
        return this.destroyed;
×
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 {
UNCOV
551
        return this.element.nativeElement;
×
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 {
UNCOV
564
        return this._interval;
×
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) {
UNCOV
577
        this._interval = +value;
×
UNCOV
578
        this.restartInterval();
×
579
    }
580

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

594

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
752
        this.initSlides(this.slides);
×
753
    }
754

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

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

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

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

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

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

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

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

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

UNCOV
827
        this.indicatorsElements[this.current].nativeElement.focus();
×
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 {
UNCOV
839
        return this.slides.find((slide) => slide.index === index);
×
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) {
UNCOV
851
        const newSlides = this.slides.toArray();
×
UNCOV
852
        newSlides.push(slide);
×
UNCOV
853
        this.slides.reset(newSlides);
×
UNCOV
854
        this.slides.notifyOnChanges();
×
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) {
UNCOV
866
        if (slide && slide === this.get(slide.index)) { // check if the requested slide for delete is present in the carousel
×
UNCOV
867
            const newSlides = this.slides.toArray();
×
UNCOV
868
            newSlides.splice(slide.index, 1);
×
UNCOV
869
            this.slides.reset(newSlides);
×
UNCOV
870
            this.slides.notifyOnChanges();
×
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) {
×
UNCOV
883
        if (slide && slide !== this.currentItem) {
×
UNCOV
884
            slide.direction = direction;
×
UNCOV
885
            slide.active = true;
×
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() {
UNCOV
898
        const index = this.getNextIndex();
×
899

UNCOV
900
        if (index === 0 && !this.loop) {
×
UNCOV
901
            this.stop();
×
UNCOV
902
            return;
×
903
        }
UNCOV
904
        return this.select(this.get(index), Direction.NEXT);
×
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() {
UNCOV
916
        const index = this.getPrevIndex();
×
917

UNCOV
918
        if (!this.loop && index === this.total - 1) {
×
UNCOV
919
            this.stop();
×
UNCOV
920
            return;
×
921
        }
UNCOV
922
        return this.select(this.get(index), Direction.PREV);
×
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() {
UNCOV
936
        if (!this.playing) {
×
UNCOV
937
            this.playing = true;
×
UNCOV
938
            this.carouselPlaying.emit(this);
×
UNCOV
939
            this.restartInterval();
×
UNCOV
940
            this.stoppedByInteraction = false;
×
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() {
UNCOV
955
        if (this.pause) {
×
UNCOV
956
            this.playing = false;
×
UNCOV
957
            this.carouselPaused.emit(this);
×
UNCOV
958
            this.resetInterval();
×
959
        }
960
    }
961

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

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

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

977
    private restartInterval() {
UNCOV
978
        this.resetInterval();
×
979

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

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

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

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

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

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

1016
    private getIndicatorsClass(): string {
UNCOV
1017
        switch (this.indicatorsOrientation) {
×
1018
            case CarouselIndicatorsOrientation.top:
1019
                return CarouselIndicatorsOrientation.start;
×
1020
            case CarouselIndicatorsOrientation.bottom:
1021
                return CarouselIndicatorsOrientation.end;
×
1022
            default:
UNCOV
1023
                return this.indicatorsOrientation;
×
1024
        }
1025
    }
1026

1027
    private getNextIndex(): number {
UNCOV
1028
        return (this.current + 1) % this.total;
×
1029
    }
1030

1031
    private getPrevIndex(): number {
UNCOV
1032
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
×
1033
    }
1034

1035
    private resetSlideStyles(slide: IgxSlideComponent) {
UNCOV
1036
        slide.nativeElement.style.transform = '';
×
UNCOV
1037
        slide.nativeElement.style.opacity = '';
×
1038
    }
1039

1040
    private pan(event) {
UNCOV
1041
        const slideSize = this.vertical
×
1042
            ? this.currentItem.nativeElement.offsetHeight
1043
            : this.currentItem.nativeElement.offsetWidth;
UNCOV
1044
        const panOffset = (slideSize / 1000);
×
UNCOV
1045
        const delta = this.vertical ? event.deltaY : event.deltaX;
×
UNCOV
1046
        const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
×
UNCOV
1047
        const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
×
1048

UNCOV
1049
        if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
×
UNCOV
1050
            return;
×
1051
        }
1052

UNCOV
1053
        if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
×
UNCOV
1054
            this.incomingSlide = null;
×
UNCOV
1055
            return;
×
1056
        }
1057

UNCOV
1058
        event.preventDefault();
×
UNCOV
1059
        if (this.isPlaying) {
×
1060
            this.stoppedByInteraction = true;
×
1061
            this.stop();
×
1062
        }
1063

UNCOV
1064
        if (this.previousItem && this.previousItem.previous) {
×
UNCOV
1065
            this.previousItem.previous = false;
×
1066
        }
UNCOV
1067
        this.finishAnimations();
×
1068

UNCOV
1069
        if (this.incomingSlide) {
×
UNCOV
1070
            if (index !== this.incomingSlide.index) {
×
UNCOV
1071
                this.resetSlideStyles(this.incomingSlide);
×
UNCOV
1072
                this.incomingSlide.previous = false;
×
UNCOV
1073
                this.incomingSlide = this.get(index);
×
1074
            }
1075
        } else {
UNCOV
1076
            this.incomingSlide = this.get(index);
×
1077
        }
UNCOV
1078
        this.incomingSlide.previous = true;
×
1079

UNCOV
1080
        if (this.animationType === CarouselAnimationType.fade) {
×
1081
            this.currentItem.nativeElement.style.opacity = `${Math.abs(offset) / slideSize}`;
×
1082
        } else {
UNCOV
1083
            this.currentItem.nativeElement.style.transform = this.vertical
×
1084
                ? `translateY(${delta}px)`
1085
                : `translateX(${delta}px)`;
UNCOV
1086
            this.incomingSlide.nativeElement.style.transform = this.vertical
×
1087
                ? `translateY(${offset}px)`
1088
                : `translateX(${offset}px)`;
1089
        }
1090
    }
1091

1092
    private unsubscriber(slide: IgxSlideComponent) {
UNCOV
1093
        return merge(this.destroy$, slide.isDestroyed);
×
1094
    }
1095

1096
    private onSlideActivated(slide: IgxSlideComponent) {
UNCOV
1097
        if (slide.active && slide !== this.currentItem) {
×
UNCOV
1098
            if (slide.direction === Direction.NONE) {
×
UNCOV
1099
                const newIndex = slide.index;
×
UNCOV
1100
                slide.direction = newIndex > this.current ? Direction.NEXT : Direction.PREV;
×
1101
            }
1102

UNCOV
1103
            if (this.currentItem) {
×
UNCOV
1104
                if (this.previousItem && this.previousItem.previous) {
×
1105
                    this.previousItem.previous = false;
×
1106
                }
UNCOV
1107
                this.currentItem.direction = slide.direction;
×
UNCOV
1108
                this.currentItem.active = false;
×
1109

UNCOV
1110
                this.previousItem = this.currentItem;
×
UNCOV
1111
                this.currentItem = slide;
×
UNCOV
1112
                this.triggerAnimations();
×
1113
            } else {
UNCOV
1114
                this.currentItem = slide;
×
1115
            }
UNCOV
1116
            this.slideChanged.emit({ carousel: this, slide });
×
UNCOV
1117
            this.restartInterval();
×
1118
        }
1119
    }
1120

1121

1122
    private finishAnimations() {
UNCOV
1123
        if (this.animationStarted(this.leaveAnimationPlayer)) {
×
1124
            this.leaveAnimationPlayer.finish();
×
1125
        }
1126

UNCOV
1127
        if (this.animationStarted(this.enterAnimationPlayer)) {
×
1128
            this.enterAnimationPlayer.finish();
×
1129
        }
1130
    }
1131

1132
    private initSlides(change: QueryList<IgxSlideComponent>) {
UNCOV
1133
        const diff = this.differ.diff(change.toArray());
×
UNCOV
1134
        if (diff) {
×
UNCOV
1135
            this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
×
UNCOV
1136
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
×
UNCOV
1137
                const slide = record.item;
×
UNCOV
1138
                slide.total = this.total;
×
UNCOV
1139
                this.slideAdded.emit({ carousel: this, slide });
×
UNCOV
1140
                if (slide.active) {
×
UNCOV
1141
                    this.currentItem = slide;
×
1142
                }
UNCOV
1143
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
×
1144
            });
1145

UNCOV
1146
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
×
UNCOV
1147
                const slide = record.item;
×
UNCOV
1148
                this.slideRemoved.emit({ carousel: this, slide });
×
UNCOV
1149
                if (slide.active) {
×
UNCOV
1150
                    slide.active = false;
×
UNCOV
1151
                    this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
×
1152
                }
1153
            });
1154

UNCOV
1155
            this.updateSlidesSelection();
×
1156
        }
1157
    }
1158

1159
    private updateSlidesSelection() {
UNCOV
1160
        if (this.platformUtil.isBrowser) {
×
UNCOV
1161
            requestAnimationFrame(() => {
×
UNCOV
1162
                if (this.currentItem) {
×
UNCOV
1163
                    this.currentItem.active = true;
×
UNCOV
1164
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
×
UNCOV
1165
                    activeSlides.forEach(slide => slide.active = false);
×
UNCOV
1166
                } else if (this.total) {
×
UNCOV
1167
                    this.slides.first.active = true;
×
1168
                }
UNCOV
1169
                this.play();
×
1170
            });
1171
        }
1172
    }
1173
    private focusSlideElement() {
UNCOV
1174
        if (this.leaveAnimationPlayer) {
×
1175
            this.leaveAnimationPlayer.animationEnd
×
1176
                .pipe(takeUntil(this.destroy$))
1177
                .subscribe(() => {
1178
                    this.slides.find(s => s.active).nativeElement.focus();
×
1179
                });
1180
        } else {
UNCOV
1181
            requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
×
1182
        }
1183
    }
1184

1185
}
1186

1187
export interface ISlideEventArgs extends IBaseEventArgs {
1188
    carousel: IgxCarouselComponent;
1189
    slide: IgxSlideComponent;
1190
}
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