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

IgniteUI / igniteui-angular / 12300330513

12 Dec 2024 04:10PM CUT coverage: 91.592% (-0.03%) from 91.621%
12300330513

push

github

web-flow
Merge pull request #15120 from IgniteUI/19.0.x

Mass Merge 19.0.x to master

12984 of 15225 branches covered (85.28%)

86 of 106 new or added lines in 16 files covered. (81.13%)

12 existing lines in 3 files now uncovered.

26320 of 28736 relevant lines covered (91.59%)

34007.75 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

196

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

459
        return this.defaultNextButton
394✔
460
    }
461

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

468
        return this.defaultPrevButton
394✔
469
    }
470

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

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

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

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

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

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

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

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

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

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

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

593

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1026
    private getNextIndex(): number {
1027
        return (this.current + 1) % this.total;
3,185✔
1028
    }
1029

1030
    private getPrevIndex(): number {
1031
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
21✔
1032
    }
1033

1034
    private resetSlideStyles(slide: IgxSlideComponent) {
1035
        slide.nativeElement.style.transform = '';
38✔
1036
        slide.nativeElement.style.opacity = '';
38✔
1037
    }
1038

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

1048
        if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
16✔
1049
            return;
2✔
1050
        }
1051

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

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

1063
        if (this.previousItem && this.previousItem.previous) {
12✔
1064
            this.previousItem.previous = false;
4✔
1065
        }
1066
        this.finishAnimations();
12✔
1067

1068
        if (this.incomingSlide) {
12✔
1069
            if (index !== this.incomingSlide.index) {
8✔
1070
                this.resetSlideStyles(this.incomingSlide);
4✔
1071
                this.incomingSlide.previous = false;
4✔
1072
                this.incomingSlide = this.get(index);
4✔
1073
            }
1074
        } else {
1075
            this.incomingSlide = this.get(index);
4✔
1076
        }
1077
        this.incomingSlide.previous = true;
12✔
1078

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

1091
    private unsubscriber(slide: IgxSlideComponent) {
1092
        return merge(this.destroy$, slide.isDestroyed);
179✔
1093
    }
1094

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

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

1109
                this.previousItem = this.currentItem;
41✔
1110
                this.currentItem = slide;
41✔
1111
                this.triggerAnimations();
41✔
1112
            } else {
1113
                this.currentItem = slide;
12✔
1114
            }
1115
            this.slideChanged.emit({ carousel: this, slide });
53✔
1116
            this.restartInterval();
53✔
1117
        }
1118
    }
1119

1120

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

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

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

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

1154
            this.updateSlidesSelection();
56✔
1155
        }
1156
    }
1157

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

1184
}
1185

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