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

IgniteUI / igniteui-angular / 16053471080

03 Jul 2025 02:41PM UTC coverage: 4.981% (-86.4%) from 91.409%
16053471080

Pull #16021

github

web-flow
Merge 7c49966eb into 7e40671a1
Pull Request #16021: fix(radio-group): dynamically added radio buttons do not initialize

178 of 15753 branches covered (1.13%)

13 of 14 new or added lines in 2 files covered. (92.86%)

25644 existing lines in 324 files now uncovered.

1478 of 29670 relevant lines covered (4.98%)

0.51 hits per line

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

1.1
/projects/igniteui-angular/src/lib/carousel/carousel.component.ts
1
import { NgClass, NgTemplateOutlet } 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
    DOCUMENT
26
} from '@angular/core';
27
import { HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
28
import { merge, Subject } from 'rxjs';
29
import { takeUntil } from 'rxjs/operators';
30
import { CarouselResourceStringsEN, ICarouselResourceStrings } from '../core/i18n/carousel-resources';
31
import { first, IBaseEventArgs, last, PlatformUtil } from '../core/utils';
32
import { IgxAngularAnimationService } from '../services/animation/angular-animation-service';
33
import { AnimationService } from '../services/animation/animation';
34
import { Direction, IgxCarouselComponentBase } from './carousel-base';
35
import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives';
36
import { IgxSlideComponent } from './slide.component';
37
import { IgxIconComponent } from '../icon/icon.component';
38
import { IgxButtonDirective } from '../directives/button/button.directive';
39
import { getCurrentResourceStrings } from '../core/i18n/resources';
40
import { HammerGesturesManager } from '../core/touch';
41
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
42
import { IgxDirectionality } from '../services/direction/directionality';
43

44
let NEXT_ID = 0;
3✔
45

46

47
@Injectable()
48
export class CarouselHammerConfig extends HammerGestureConfig {
3✔
49
    public override overrides = {
×
50
        pan: { direction: HammerGesturesManager.Hammer?.DIRECTION_HORIZONTAL }
51
    };
52
}
53
/**
54
 * **Ignite UI for Angular Carousel** -
55
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel.html)
56
 *
57
 * The Ignite UI Carousel is used to browse or navigate through a collection of slides. Slides can contain custom
58
 * content such as images or cards and be used for things such as on-boarding tutorials or page-based interfaces.
59
 * It can be used as a separate fullscreen element or inside another component.
60
 *
61
 * Example:
62
 * ```html
63
 * <igx-carousel>
64
 *   <igx-slide>
65
 *     <h3>First Slide Header</h3>
66
 *     <p>First slide Content</p>
67
 *   <igx-slide>
68
 *   <igx-slide>
69
 *     <h3>Second Slide Header</h3>
70
 *     <p>Second Slide Content</p>
71
 * </igx-carousel>
72
 * ```
73
 */
74
@Component({
75
    providers: [
76
        {
77
            provide: HAMMER_GESTURE_CONFIG,
78
            useClass: CarouselHammerConfig
79
        }
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, NgClass, NgTemplateOutlet]
89
})
90
export class IgxCarouselComponent extends IgxCarouselComponentBase implements OnDestroy, AfterContentInit {
3✔
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()
UNCOV
103
    public id = `igx-carousel-${NEXT_ID++}`;
×
104
    /**
105
     * Returns the `role` attribute of the carousel.
106
     * ```typescript
107
     * let carouselRole =  this.carousel.role;
108
     * ```
109
     *
110
     * @memberof IgxCarouselComponent
111
     */
UNCOV
112
    @HostBinding('attr.role') public role = 'region';
×
113

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

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

124
    /** @hidden */
125
    @HostBinding('class.igx-carousel--vertical')
126
        public get isVertical(): boolean {
UNCOV
127
                return this.vertical;
×
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')
UNCOV
139
    public cssClass = 'igx-carousel';
×
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() {
UNCOV
149
        return this.gesturesSupport ? 'pan-y' : 'auto';
×
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
     */
UNCOV
161
    @Input({ transform: booleanAttribute }) public loop = true;
×
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
     */
UNCOV
172
    @Input({ transform: booleanAttribute }) public pause = true;
×
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
     */
UNCOV
183
    @Input({ transform: booleanAttribute }) public navigation = true;
×
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
     */
UNCOV
194
    @Input({ transform: booleanAttribute }) public indicators = true;
×
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
     */
UNCOV
206
    @Input({ transform: booleanAttribute }) public override vertical = false;
×
207

208
    /**
209
     * Controls whether the carousel should support gestures.
210
     * Default value is `true`.
211
     * ```html
212
     * <igx-carousel [gesturesSupport]="false"></igx-carousel>
213
     * ```
214
     *
215
     * @memberOf IgxCarouselComponent
216
     */
UNCOV
217
    @Input({ transform: booleanAttribute }) public gesturesSupport = true;
×
218

219
    /**
220
     * Controls the maximum indexes that can be shown.
221
     * Default value is `10`.
222
     * ```html
223
     * <igx-carousel [maximumIndicatorsCount]="5"></igx-carousel>
224
     * ```
225
     *
226
     * @memberOf IgxCarouselComponent
227
     */
UNCOV
228
    @Input() public maximumIndicatorsCount = 10;
×
229

230
    /**
231
     * Gets/sets the display mode of carousel indicators. It can be `start` or `end`.
232
     * Default value is `end`.
233
     * ```html
234
     * <igx-carousel indicatorsOrientation="start">
235
     * <igx-carousel>
236
     * ```
237
     *
238
     * @memberOf IgxCarouselComponent
239
     */
UNCOV
240
    @Input() public indicatorsOrientation: CarouselIndicatorsOrientation = CarouselIndicatorsOrientation.end;
×
241

242
    /**
243
     * Gets/sets the animation type of carousel.
244
     * Default value is `slide`.
245
     * ```html
246
     * <igx-carousel animationType="none">
247
     * <igx-carousel>
248
     * ```
249
     *
250
     * @memberOf IgxCarouselComponent
251
     */
UNCOV
252
    @Input() public override animationType: CarouselAnimationType = CarouselAnimationType.slide;
×
253

254
    /**
255
     * The custom template, if any, that should be used when rendering carousel indicators
256
     *
257
     * ```typescript
258
     * // Set in typescript
259
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
260
     * myComponent.carousel.indicatorTemplate = myCustomTemplate;
261
     * ```
262
     * ```html
263
     * <!-- Set in markup -->
264
     *  <igx-carousel #carousel>
265
     *      ...
266
     *      <ng-template igxCarouselIndicator let-slide>
267
     *         <igx-icon *ngIf="slide.active">brightness_7</igx-icon>
268
     *         <igx-icon *ngIf="!slide.active">brightness_5</igx-icon>
269
     *      </ng-template>
270
     *  </igx-carousel>
271
     * ```
272
     */
273
    @ContentChild(IgxCarouselIndicatorDirective, { read: TemplateRef, static: false })
UNCOV
274
    public indicatorTemplate: TemplateRef<any> = null;
×
275

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

299
    /**
300
     * The custom template, if any, that should be used when rendering carousel previous button
301
     *
302
     * ```typescript
303
     * // Set in typescript
304
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
305
     * myComponent.carousel.prevButtonTemplate = myCustomTemplate;
306
     * ```
307
     * ```html
308
     * <!-- Set in markup -->
309
     *  <igx-carousel #carousel>
310
     *      ...
311
     *      <ng-template igxCarouselPrevButton let-disabled>
312
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
313
     *              <igx-icon name="remove"></igx-icon>
314
     *          </button>
315
     *      </ng-template>
316
     *  </igx-carousel>
317
     * ```
318
     */
319
    @ContentChild(IgxCarouselPrevButtonDirective, { read: TemplateRef, static: false })
UNCOV
320
    public prevButtonTemplate: TemplateRef<any> = null;
×
321

322
    /**
323
     * The collection of `slides` currently in the carousel.
324
     * ```typescript
325
     * let slides: QueryList<IgxSlideComponent> = this.carousel.slides;
326
     * ```
327
     *
328
     * @memberOf IgxCarouselComponent
329
     */
330
    @ContentChildren(IgxSlideComponent)
331
    public slides: QueryList<IgxSlideComponent>;
332

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

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

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

366
    /**
367
     * An event that is emitted after the carousel has been paused.
368
     * Provides a reference to the `IgxCarouselComponent` as an event argument.
369
     * ```html
370
     * <igx-carousel (carouselPaused)="carouselPaused($event)"></igx-carousel>
371
     * ```
372
     *
373
     * @memberOf IgxCarouselComponent
374
     */
UNCOV
375
    @Output() public carouselPaused = new EventEmitter<IgxCarouselComponent>();
×
376

377
    /**
378
     * An event that is emitted after the carousel has resumed transitioning between `slides`.
379
     * Provides a reference to the `IgxCarouselComponent` as an event argument.
380
     * ```html
381
     * <igx-carousel (carouselPlaying)="carouselPlaying($event)"></igx-carousel>
382
     * ```
383
     *
384
     * @memberOf IgxCarouselComponent
385
     */
UNCOV
386
    @Output() public carouselPlaying = new EventEmitter<IgxCarouselComponent>();
×
387

388
    @ViewChild('defaultIndicator', { read: TemplateRef, static: true })
389
    private defaultIndicator: TemplateRef<any>;
390

391
    @ViewChild('defaultNextButton', { read: TemplateRef, static: true })
392
    private defaultNextButton: TemplateRef<any>;
393

394
    @ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
395
    private defaultPrevButton: TemplateRef<any>;
396

397
    @ViewChildren('indicators', { read: ElementRef })
398
    private _indicators: QueryList<ElementRef<HTMLDivElement>>;
399

400
    /**
401
     * @hidden
402
     * @internal
403
     */
404
    public stoppedByInteraction: boolean;
405
    protected override currentItem: IgxSlideComponent;
406
    protected override previousItem: IgxSlideComponent;
407
    private _interval: number;
UNCOV
408
    private _resourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN);
×
409
    private lastInterval: any;
410
    private playing: boolean;
411
    private destroyed: boolean;
UNCOV
412
    private destroy$ = new Subject<any>();
×
UNCOV
413
    private differ: IterableDiffer<IgxSlideComponent> | null = null;
×
414
    private incomingSlide: IgxSlideComponent;
UNCOV
415
    private _hasKeyboardFocusOnIndicators = false;
×
416

417
    /**
418
     * An accessor that sets the resource strings.
419
     * By default it uses EN resources.
420
     */
421
    @Input()
422
    public set resourceStrings(value: ICarouselResourceStrings) {
423
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
424
    }
425

426
    /**
427
     * An accessor that returns the resource strings.
428
     */
429
    public get resourceStrings(): ICarouselResourceStrings {
UNCOV
430
        return this._resourceStrings;
×
431
    }
432

433
    /** @hidden */
434
    public get getIndicatorTemplate(): TemplateRef<any> {
UNCOV
435
        if (this.indicatorTemplate) {
×
UNCOV
436
            return this.indicatorTemplate;
×
437
        }
UNCOV
438
        return this.defaultIndicator;
×
439
    }
440

441
    /** @hidden */
442
    public get getNextButtonTemplate(): TemplateRef<any> {
UNCOV
443
        if (this.nextButtonTemplate) {
×
UNCOV
444
            return this.nextButtonTemplate;
×
445
        }
446

UNCOV
447
        return this.defaultNextButton
×
448
    }
449

450
    /** @hidden */
451
    public get getPrevButtonTemplate(): TemplateRef<any> {
UNCOV
452
        if (this.prevButtonTemplate) {
×
UNCOV
453
            return this.prevButtonTemplate;
×
454
        }
455

UNCOV
456
        return this.defaultPrevButton
×
457
    }
458

459
    /** @hidden */
460
    public get indicatorsClass() {
UNCOV
461
        return {
×
462
            ['igx-carousel-indicators--focused']: this._hasKeyboardFocusOnIndicators,
463
            [`igx-carousel-indicators--${this.getIndicatorsClass()}`]: true
464
        };
465
    }
466

467
    /** @hidden */
468
    public get showIndicators(): boolean {
UNCOV
469
        return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
×
470
    }
471

472
    /** @hidden */
473
    public get showIndicatorsLabel(): boolean {
UNCOV
474
        return this.indicators && this.total > this.maximumIndicatorsCount;
×
475
    }
476

477
    /** @hidden */
478
    public get getCarouselLabel() {
UNCOV
479
        return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
×
480
    }
481

482
    /**
483
     * Returns the total number of `slides` in the carousel.
484
     * ```typescript
485
     * let slideCount =  this.carousel.total;
486
     * ```
487
     *
488
     * @memberOf IgxCarouselComponent
489
     */
490
    public get total(): number {
UNCOV
491
        return this.slides?.length;
×
492
    }
493

494
    /**
495
     * The index of the slide being currently shown.
496
     * ```typescript
497
     * let currentSlideNumber =  this.carousel.current;
498
     * ```
499
     *
500
     * @memberOf IgxCarouselComponent
501
     */
502
    public get current(): number {
UNCOV
503
        return !this.currentItem ? 0 : this.currentItem.index;
×
504
    }
505

506
    /**
507
     * Returns a boolean indicating if the carousel is playing.
508
     * ```typescript
509
     * let isPlaying =  this.carousel.isPlaying;
510
     * ```
511
     *
512
     * @memberOf IgxCarouselComponent
513
     */
514
    public get isPlaying(): boolean {
UNCOV
515
        return this.playing;
×
516
    }
517

518
    /**
519
     * Returns а boolean indicating if the carousel is destroyed.
520
     * ```typescript
521
     * let isDestroyed =  this.carousel.isDestroyed;
522
     * ```
523
     *
524
     * @memberOf IgxCarouselComponent
525
     */
526
    public get isDestroyed(): boolean {
UNCOV
527
        return this.destroyed;
×
528
    }
529
    /**
530
     * Returns a reference to the carousel element in the DOM.
531
     * ```typescript
532
     * let nativeElement =  this.carousel.nativeElement;
533
     * ```
534
     *
535
     * @memberof IgxCarouselComponent
536
     */
537
    public get nativeElement(): any {
UNCOV
538
        return this.element.nativeElement;
×
539
    }
540

541
    /**
542
     * Returns the time `interval` in milliseconds before the slide changes.
543
     * ```typescript
544
     * let timeInterval = this.carousel.interval;
545
     * ```
546
     *
547
     * @memberof IgxCarouselComponent
548
     */
549
    @Input()
550
    public get interval(): number {
UNCOV
551
        return this._interval;
×
552
    }
553

554
    /**
555
     * Sets the time `interval` in milliseconds before the slide changes.
556
     * If not set, the carousel will not change `slides` automatically.
557
     * ```html
558
     * <igx-carousel [interval]="1000"></igx-carousel>
559
     * ```
560
     *
561
     * @memberof IgxCarouselComponent
562
     */
563
    public set interval(value: number) {
UNCOV
564
        this._interval = +value;
×
UNCOV
565
        this.restartInterval();
×
566
    }
567

568
    constructor(
569
        cdr: ChangeDetectorRef,
UNCOV
570
        private element: ElementRef,
×
UNCOV
571
        private iterableDiffers: IterableDiffers,
×
572
        @Inject(IgxAngularAnimationService) animationService: AnimationService,
UNCOV
573
        private platformUtil: PlatformUtil,
×
UNCOV
574
        private dir: IgxDirectionality,
×
UNCOV
575
        @Inject(DOCUMENT) private document: any
×
576
    ) {
UNCOV
577
        super(animationService, cdr);
×
UNCOV
578
        this.differ = this.iterableDiffers.find([]).create(null);
×
579
    }
580

581
    /** @hidden */
582
    @HostListener('tap', ['$event'])
583
    public onTap(event) {
584
        // play pause only when tap on slide
UNCOV
585
        if (event.target && event.target.classList.contains('igx-slide')) {
×
UNCOV
586
            if (this.isPlaying) {
×
UNCOV
587
                if (this.pause) {
×
UNCOV
588
                    this.stoppedByInteraction = true;
×
589
                }
UNCOV
590
                this.stop();
×
UNCOV
591
            } else if (this.stoppedByInteraction) {
×
UNCOV
592
                this.play();
×
593
            }
594
        }
595
    }
596

597
    /** @hidden */
598
    @HostListener('mouseenter')
599
    public onMouseEnter() {
UNCOV
600
        if (this.pause && this.isPlaying) {
×
UNCOV
601
            this.stoppedByInteraction = true;
×
602
        }
UNCOV
603
        this.stop();
×
604
    }
605

606
    /** @hidden */
607
    @HostListener('mouseleave')
608
    public onMouseLeave() {
UNCOV
609
        if (this.stoppedByInteraction) {
×
UNCOV
610
            this.play();
×
611
        }
612
    }
613

614
    /** @hidden */
615
    @HostListener('panleft', ['$event'])
616
    public onPanLeft(event) {
UNCOV
617
        if (!this.vertical) {
×
UNCOV
618
            this.pan(event);
×
619
        }
620
    }
621

622
    /** @hidden */
623
    @HostListener('panright', ['$event'])
624
    public onPanRight(event) {
UNCOV
625
        if (!this.vertical) {
×
UNCOV
626
            this.pan(event);
×
627
        }
628
    }
629

630
    /** @hidden */
631
    @HostListener('panup', ['$event'])
632
    public onPanUp(event) {
UNCOV
633
        if (this.vertical) {
×
UNCOV
634
            this.pan(event);
×
635
        }
636
    }
637

638
    /** @hidden */
639
    @HostListener('pandown', ['$event'])
640
    public onPanDown(event) {
UNCOV
641
        if (this.vertical) {
×
UNCOV
642
            this.pan(event);
×
643
        }
644
    }
645

646
    /**
647
     * @hidden
648
     */
649
    @HostListener('panend', ['$event'])
650
    public onPanEnd(event) {
UNCOV
651
        if (!this.gesturesSupport) {
×
UNCOV
652
            return;
×
653
        }
UNCOV
654
        event.preventDefault();
×
655

UNCOV
656
        const slideSize = this.vertical
×
657
            ? this.currentItem.nativeElement.offsetHeight
658
            : this.currentItem.nativeElement.offsetWidth;
UNCOV
659
        const panOffset = (slideSize / 1000);
×
UNCOV
660
        const eventDelta = this.vertical ? event.deltaY : event.deltaX;
×
UNCOV
661
        const delta = Math.abs(eventDelta) + panOffset < slideSize ? Math.abs(eventDelta) : slideSize - panOffset;
×
UNCOV
662
        const velocity = Math.abs(event.velocity);
×
UNCOV
663
        this.resetSlideStyles(this.currentItem);
×
UNCOV
664
        if (this.incomingSlide) {
×
UNCOV
665
            this.resetSlideStyles(this.incomingSlide);
×
UNCOV
666
            if (slideSize / 2 < delta || velocity > 1) {
×
UNCOV
667
                this.incomingSlide.direction = eventDelta < 0 ? Direction.NEXT : Direction.PREV;
×
UNCOV
668
                this.incomingSlide.previous = false;
×
669

UNCOV
670
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
×
671
                    delta / slideSize : (slideSize - delta) / slideSize;
672

UNCOV
673
                if (velocity > 1) {
×
UNCOV
674
                    this.newDuration = this.defaultAnimationDuration / velocity;
×
675
                }
UNCOV
676
                this.incomingSlide.active = true;
×
677
            } else {
UNCOV
678
                this.currentItem.direction = eventDelta > 0 ? Direction.NEXT : Direction.PREV;
×
UNCOV
679
                this.previousItem = this.incomingSlide;
×
UNCOV
680
                this.previousItem.previous = true;
×
UNCOV
681
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
×
682
                    Math.abs((slideSize - delta) / slideSize) : delta / slideSize;
UNCOV
683
                this.playAnimations();
×
684
            }
685
        }
686

UNCOV
687
        if (this.stoppedByInteraction) {
×
688
            this.play();
×
689
        }
690
    }
691

692
    /** @hidden */
693
    public ngAfterContentInit() {
UNCOV
694
        this.slides.changes
×
695
            .pipe(takeUntil(this.destroy$))
UNCOV
696
            .subscribe((change: QueryList<IgxSlideComponent>) => this.initSlides(change));
×
697

UNCOV
698
        this.initSlides(this.slides);
×
699
    }
700

701
    /** @hidden */
702
    public override ngOnDestroy() {
UNCOV
703
        super.ngOnDestroy();
×
UNCOV
704
        this.destroy$.next(true);
×
UNCOV
705
        this.destroy$.complete();
×
UNCOV
706
        this.destroyed = true;
×
UNCOV
707
        if (this.lastInterval) {
×
UNCOV
708
            clearInterval(this.lastInterval);
×
709
        }
710
    }
711

712
    /** @hidden */
713
    public handleKeydownPrev(event: KeyboardEvent): void {
UNCOV
714
        if (this.platformUtil.isActivationKey(event)) {
×
UNCOV
715
            event.preventDefault();
×
UNCOV
716
            this.prev();
×
717
        }
718
    }
719

720
    /** @hidden */
721
    public handleKeydownNext(event: KeyboardEvent): void {
UNCOV
722
        if (this.platformUtil.isActivationKey(event)) {
×
UNCOV
723
            event.preventDefault();
×
UNCOV
724
            this.next();
×
725
        }
726
    }
727

728
    /** @hidden */
729
    public handleKeyUp(event: KeyboardEvent): void {
UNCOV
730
        if (event.key === this.platformUtil.KEYMAP.TAB) {
×
UNCOV
731
            this._hasKeyboardFocusOnIndicators = true;
×
732
        }
733
    }
734

735
    /** @hidden */
736
    public handleFocusOut(event: FocusEvent): void {
UNCOV
737
        const target = event.relatedTarget as HTMLElement;
×
738

UNCOV
739
        if (!target || !target.classList.contains('igx-carousel-indicators__indicator')) {
×
UNCOV
740
            this._hasKeyboardFocusOnIndicators = false;
×
741
        }
742
    }
743

744
    /** @hidden */
745
    public handleClick(): void {
UNCOV
746
        this._hasKeyboardFocusOnIndicators = false;
×
747
    }
748

749
    /** @hidden */
750
    public handleKeydown(event: KeyboardEvent): void {
UNCOV
751
        const { key } = event;
×
UNCOV
752
        const slides = this.slides.toArray();
×
753

UNCOV
754
        switch (key) {
×
755
            case this.platformUtil.KEYMAP.ARROW_LEFT:
UNCOV
756
                this.dir.rtl ? this.next() : this.prev();
×
UNCOV
757
                break;
×
758
            case this.platformUtil.KEYMAP.ARROW_RIGHT:
UNCOV
759
                this.dir.rtl ? this.prev() : this.next();
×
UNCOV
760
                break;
×
761
            case this.platformUtil.KEYMAP.HOME:
UNCOV
762
                event.preventDefault();
×
UNCOV
763
                this.select(this.dir.rtl ? last(slides) : first(slides));
×
UNCOV
764
                break;
×
765
            case this.platformUtil.KEYMAP.END:
UNCOV
766
                event.preventDefault();
×
UNCOV
767
                this.select(this.dir.rtl ? first(slides) : last(slides));
×
UNCOV
768
                break;
×
769
        }
770

UNCOV
771
        this.indicatorsElements[this.current].nativeElement.focus();
×
772
    }
773

774
    /**
775
     * Returns the slide corresponding to the provided `index` or null.
776
     * ```typescript
777
     * let slide1 =  this.carousel.get(1);
778
     * ```
779
     *
780
     * @memberOf IgxCarouselComponent
781
     */
782
    public get(index: number): IgxSlideComponent {
UNCOV
783
        return this.slides.find((slide) => slide.index === index);
×
784
    }
785

786
    /**
787
     * Adds a new slide to the carousel.
788
     * ```typescript
789
     * this.carousel.add(newSlide);
790
     * ```
791
     *
792
     * @memberOf IgxCarouselComponent
793
     */
794
    public add(slide: IgxSlideComponent) {
UNCOV
795
        const newSlides = this.slides.toArray();
×
UNCOV
796
        newSlides.push(slide);
×
UNCOV
797
        this.slides.reset(newSlides);
×
UNCOV
798
        this.slides.notifyOnChanges();
×
799
    }
800

801
    /**
802
     * Removes a slide from the carousel.
803
     * ```typescript
804
     * this.carousel.remove(slide);
805
     * ```
806
     *
807
     * @memberOf IgxCarouselComponent
808
     */
809
    public remove(slide: IgxSlideComponent) {
UNCOV
810
        if (slide && slide === this.get(slide.index)) { // check if the requested slide for delete is present in the carousel
×
UNCOV
811
            const newSlides = this.slides.toArray();
×
UNCOV
812
            newSlides.splice(slide.index, 1);
×
UNCOV
813
            this.slides.reset(newSlides);
×
UNCOV
814
            this.slides.notifyOnChanges();
×
815
        }
816
    }
817

818
    /**
819
     * Kicks in a transition for a given slide with a given `direction`.
820
     * ```typescript
821
     * this.carousel.select(this.carousel.get(2), Direction.NEXT);
822
     * ```
823
     *
824
     * @memberOf IgxCarouselComponent
825
     */
826
    public select(slide: IgxSlideComponent, direction: Direction = Direction.NONE) {
×
UNCOV
827
        if (slide && slide !== this.currentItem) {
×
UNCOV
828
            slide.direction = direction;
×
UNCOV
829
            slide.active = true;
×
830
        }
831
    }
832

833
    /**
834
     * Transitions to the next slide in the carousel.
835
     * ```typescript
836
     * this.carousel.next();
837
     * ```
838
     *
839
     * @memberOf IgxCarouselComponent
840
     */
841
    public next() {
UNCOV
842
        const index = this.getNextIndex();
×
843

UNCOV
844
        if (index === 0 && !this.loop) {
×
UNCOV
845
            this.stop();
×
UNCOV
846
            return;
×
847
        }
UNCOV
848
        return this.select(this.get(index), Direction.NEXT);
×
849
    }
850

851
    /**
852
     * Transitions to the previous slide in the carousel.
853
     * ```typescript
854
     * this.carousel.prev();
855
     * ```
856
     *
857
     * @memberOf IgxCarouselComponent
858
     */
859
    public prev() {
UNCOV
860
        const index = this.getPrevIndex();
×
861

UNCOV
862
        if (!this.loop && index === this.total - 1) {
×
UNCOV
863
            this.stop();
×
UNCOV
864
            return;
×
865
        }
UNCOV
866
        return this.select(this.get(index), Direction.PREV);
×
867
    }
868

869
    /**
870
     * Resumes playing of the carousel if in paused state.
871
     * No operation otherwise.
872
     * ```typescript
873
     * this.carousel.play();
874
     * }
875
     * ```
876
     *
877
     * @memberOf IgxCarouselComponent
878
     */
879
    public play() {
UNCOV
880
        if (!this.playing) {
×
UNCOV
881
            this.playing = true;
×
UNCOV
882
            this.carouselPlaying.emit(this);
×
UNCOV
883
            this.restartInterval();
×
UNCOV
884
            this.stoppedByInteraction = false;
×
885
        }
886
    }
887

888
    /**
889
     * Stops slide transitions if the `pause` option is set to `true`.
890
     * No operation otherwise.
891
     * ```typescript
892
     *  this.carousel.stop();
893
     * }
894
     * ```
895
     *
896
     * @memberOf IgxCarouselComponent
897
     */
898
    public stop() {
UNCOV
899
        if (this.pause) {
×
UNCOV
900
            this.playing = false;
×
UNCOV
901
            this.carouselPaused.emit(this);
×
UNCOV
902
            this.resetInterval();
×
903
        }
904
    }
905

906
    protected getPreviousElement(): HTMLElement {
UNCOV
907
        return this.previousItem.nativeElement;
×
908
    }
909

910
    protected getCurrentElement(): HTMLElement {
UNCOV
911
        return this.currentItem.nativeElement;
×
912
    }
913

914
    private resetInterval() {
UNCOV
915
        if (this.lastInterval) {
×
UNCOV
916
            clearInterval(this.lastInterval);
×
UNCOV
917
            this.lastInterval = null;
×
918
        }
919
    }
920

921
    private restartInterval() {
UNCOV
922
        this.resetInterval();
×
923

UNCOV
924
        if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
×
UNCOV
925
            this.lastInterval = setInterval(() => {
×
UNCOV
926
                const tick = +this.interval;
×
UNCOV
927
                if (this.playing && this.total && !isNaN(tick) && tick > 0) {
×
UNCOV
928
                    this.next();
×
929
                } else {
930
                    this.stop();
×
931
                }
932
            }, this.interval);
933
        }
934
    }
935

936
    /** @hidden */
937
    public get nextButtonDisabled() {
UNCOV
938
        return !this.loop && this.current === (this.total - 1);
×
939
    }
940

941
    /** @hidden */
942
    public get prevButtonDisabled() {
UNCOV
943
        return !this.loop && this.current === 0;
×
944
    }
945

946
    private get indicatorsElements() {
UNCOV
947
        return this._indicators.toArray();
×
948
    }
949

950
    private getIndicatorsClass(): string {
UNCOV
951
        switch (this.indicatorsOrientation) {
×
952
            case CarouselIndicatorsOrientation.top:
953
                return CarouselIndicatorsOrientation.start;
×
954
            case CarouselIndicatorsOrientation.bottom:
955
                return CarouselIndicatorsOrientation.end;
×
956
            default:
UNCOV
957
                return this.indicatorsOrientation;
×
958
        }
959
    }
960

961
    private getNextIndex(): number {
UNCOV
962
        return (this.current + 1) % this.total;
×
963
    }
964

965
    private getPrevIndex(): number {
UNCOV
966
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
×
967
    }
968

969
    private resetSlideStyles(slide: IgxSlideComponent) {
UNCOV
970
        slide.nativeElement.style.transform = '';
×
UNCOV
971
        slide.nativeElement.style.opacity = '';
×
972
    }
973

974
    private pan(event) {
UNCOV
975
        const slideSize = this.vertical
×
976
            ? this.currentItem.nativeElement.offsetHeight
977
            : this.currentItem.nativeElement.offsetWidth;
UNCOV
978
        const panOffset = (slideSize / 1000);
×
UNCOV
979
        const delta = this.vertical ? event.deltaY : event.deltaX;
×
UNCOV
980
        const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
×
UNCOV
981
        const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
×
982

UNCOV
983
        if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
×
UNCOV
984
            return;
×
985
        }
986

UNCOV
987
        if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
×
UNCOV
988
            this.incomingSlide = null;
×
UNCOV
989
            return;
×
990
        }
991

UNCOV
992
        event.preventDefault();
×
UNCOV
993
        if (this.isPlaying) {
×
994
            this.stoppedByInteraction = true;
×
995
            this.stop();
×
996
        }
997

UNCOV
998
        if (this.previousItem && this.previousItem.previous) {
×
UNCOV
999
            this.previousItem.previous = false;
×
1000
        }
UNCOV
1001
        this.finishAnimations();
×
1002

UNCOV
1003
        if (this.incomingSlide) {
×
UNCOV
1004
            if (index !== this.incomingSlide.index) {
×
UNCOV
1005
                this.resetSlideStyles(this.incomingSlide);
×
UNCOV
1006
                this.incomingSlide.previous = false;
×
UNCOV
1007
                this.incomingSlide = this.get(index);
×
1008
            }
1009
        } else {
UNCOV
1010
            this.incomingSlide = this.get(index);
×
1011
        }
UNCOV
1012
        this.incomingSlide.previous = true;
×
1013

UNCOV
1014
        if (this.animationType === CarouselAnimationType.fade) {
×
1015
            this.currentItem.nativeElement.style.opacity = `${Math.abs(offset) / slideSize}`;
×
1016
        } else {
UNCOV
1017
            this.currentItem.nativeElement.style.transform = this.vertical
×
1018
                ? `translateY(${delta}px)`
1019
                : `translateX(${delta}px)`;
UNCOV
1020
            this.incomingSlide.nativeElement.style.transform = this.vertical
×
1021
                ? `translateY(${offset}px)`
1022
                : `translateX(${offset}px)`;
1023
        }
1024
    }
1025

1026
    private unsubscriber(slide: IgxSlideComponent) {
UNCOV
1027
        return merge(this.destroy$, slide.isDestroyed);
×
1028
    }
1029

1030
    private onSlideActivated(slide: IgxSlideComponent) {
UNCOV
1031
        if (slide.active && slide !== this.currentItem) {
×
UNCOV
1032
            if (slide.direction === Direction.NONE) {
×
UNCOV
1033
                const newIndex = slide.index;
×
UNCOV
1034
                slide.direction = newIndex > this.current ? Direction.NEXT : Direction.PREV;
×
1035
            }
1036

UNCOV
1037
            if (this.currentItem) {
×
UNCOV
1038
                if (this.previousItem && this.previousItem.previous) {
×
1039
                    this.previousItem.previous = false;
×
1040
                }
UNCOV
1041
                this.currentItem.direction = slide.direction;
×
UNCOV
1042
                this.currentItem.active = false;
×
1043

UNCOV
1044
                this.previousItem = this.currentItem;
×
UNCOV
1045
                this.currentItem = slide;
×
UNCOV
1046
                this.triggerAnimations();
×
1047
            } else {
UNCOV
1048
                this.currentItem = slide;
×
1049
            }
UNCOV
1050
            this.slideChanged.emit({ carousel: this, slide });
×
UNCOV
1051
            this.restartInterval();
×
UNCOV
1052
            this.cdr.markForCheck();
×
1053
        }
1054
    }
1055

1056

1057
    private finishAnimations() {
UNCOV
1058
        if (this.animationStarted(this.leaveAnimationPlayer)) {
×
1059
            this.leaveAnimationPlayer.finish();
×
1060
        }
1061

UNCOV
1062
        if (this.animationStarted(this.enterAnimationPlayer)) {
×
1063
            this.enterAnimationPlayer.finish();
×
1064
        }
1065
    }
1066

1067
    private initSlides(change: QueryList<IgxSlideComponent>) {
UNCOV
1068
        const diff = this.differ.diff(change.toArray());
×
UNCOV
1069
        if (diff) {
×
UNCOV
1070
            this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
×
UNCOV
1071
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
×
UNCOV
1072
                const slide = record.item;
×
UNCOV
1073
                slide.total = this.total;
×
UNCOV
1074
                this.slideAdded.emit({ carousel: this, slide });
×
UNCOV
1075
                if (slide.active) {
×
UNCOV
1076
                    this.currentItem = slide;
×
1077
                }
UNCOV
1078
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
×
1079
            });
1080

UNCOV
1081
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
×
UNCOV
1082
                const slide = record.item;
×
UNCOV
1083
                this.slideRemoved.emit({ carousel: this, slide });
×
UNCOV
1084
                if (slide.active) {
×
UNCOV
1085
                    slide.active = false;
×
UNCOV
1086
                    this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
×
1087
                }
1088
            });
1089

UNCOV
1090
            this.updateSlidesSelection();
×
1091
        }
1092
    }
1093

1094
    private updateSlidesSelection() {
UNCOV
1095
        if (this.platformUtil.isBrowser) {
×
UNCOV
1096
            requestAnimationFrame(() => {
×
UNCOV
1097
                if (this.currentItem) {
×
UNCOV
1098
                    this.currentItem.active = true;
×
UNCOV
1099
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
×
UNCOV
1100
                    activeSlides.forEach(slide => slide.active = false);
×
UNCOV
1101
                } else if (this.total) {
×
UNCOV
1102
                    this.slides.first.active = true;
×
1103
                }
UNCOV
1104
                this.play();
×
1105
            });
1106
        }
1107
    }
1108
}
1109

1110
export interface ISlideEventArgs extends IBaseEventArgs {
1111
    carousel: IgxCarouselComponent;
1112
    slide: IgxSlideComponent;
1113
}
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