igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,267 lines (1,262 loc) • 61.2 kB
JavaScript
import { NgClass, NgTemplateOutlet } from '@angular/common';
import * as i0 from '@angular/core';
import { inject, ChangeDetectorRef, EventEmitter, Directive, ElementRef, booleanAttribute, Output, HostBinding, Input, Component, Injectable, IterableDiffers, DOCUMENT, TemplateRef, HostListener, ViewChildren, ViewChild, ContentChildren, ContentChild, NgModule } from '@angular/core';
import { HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
import { Subject, merge } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { IgxAngularAnimationService, HammerGesturesManager, PlatformUtil, ɵIgxDirectionality as _IgxDirectionality, getCurrentResourceStrings, CarouselResourceStringsEN, first, last } from 'igniteui-angular/core';
import { useAnimation } from '@angular/animations';
import { fadeIn, slideInLeft } from 'igniteui-angular/animations';
import { IgxIconComponent } from 'igniteui-angular/icon';
import { IgxButtonDirective } from 'igniteui-angular/directives';
const CarouselAnimationType = {
none: 'none',
slide: 'slide',
fade: 'fade'
};
const CarouselIndicatorsOrientation = {
/**
* @deprecated in version 19.1.0. Use `end` instead.
*/
bottom: 'bottom',
/**
* @deprecated in version 19.1.0. Use `start` instead.
*/
top: 'top',
start: 'start',
end: 'end'
};
var CarouselAnimationDirection;
(function (CarouselAnimationDirection) {
CarouselAnimationDirection[CarouselAnimationDirection["NONE"] = 0] = "NONE";
CarouselAnimationDirection[CarouselAnimationDirection["NEXT"] = 1] = "NEXT";
CarouselAnimationDirection[CarouselAnimationDirection["PREV"] = 2] = "PREV";
})(CarouselAnimationDirection || (CarouselAnimationDirection = {}));
/** @hidden */
class IgxCarouselComponentBase {
constructor() {
this.animationService = inject(IgxAngularAnimationService);
this.cdr = inject(ChangeDetectorRef);
/** @hidden */
this.animationType = CarouselAnimationType.slide;
/** @hidden @internal */
this.enterAnimationDone = new EventEmitter();
/** @hidden @internal */
this.leaveAnimationDone = new EventEmitter();
/** @hidden */
this.defaultAnimationDuration = 320;
/** @hidden */
this.animationPosition = 0;
/** @hidden */
this.newDuration = 0;
/** @hidden */
this.vertical = false;
}
ngOnDestroy() {
if (this.enterAnimationPlayer) {
this.enterAnimationPlayer.destroy();
this.enterAnimationPlayer = null;
}
if (this.leaveAnimationPlayer) {
this.leaveAnimationPlayer.destroy();
this.leaveAnimationPlayer = null;
}
}
/** @hidden */
triggerAnimations() {
if (this.animationType !== CarouselAnimationType.none) {
if (this.animationStarted(this.leaveAnimationPlayer) || this.animationStarted(this.enterAnimationPlayer)) {
requestAnimationFrame(() => {
this.resetAnimations();
this.playAnimations();
});
}
else {
this.playAnimations();
}
}
}
/** @hidden */
animationStarted(animation) {
return animation && animation.hasStarted();
}
/** @hidden */
playAnimations() {
this.playLeaveAnimation();
this.playEnterAnimation();
}
resetAnimations() {
if (this.animationStarted(this.leaveAnimationPlayer)) {
this.leaveAnimationPlayer.reset();
this.leaveAnimationDone.emit();
}
if (this.animationStarted(this.enterAnimationPlayer)) {
this.enterAnimationPlayer.reset();
this.enterAnimationDone.emit();
this.cdr.markForCheck();
}
}
getAnimation() {
let duration;
if (this.newDuration) {
duration = this.animationPosition ? this.animationPosition * this.newDuration : this.newDuration;
}
else {
duration = this.animationPosition ? this.animationPosition * this.defaultAnimationDuration : this.defaultAnimationDuration;
}
const trans = this.animationPosition ? this.animationPosition * 100 : 100;
switch (this.animationType) {
case CarouselAnimationType.slide:
return {
enterAnimation: useAnimation(slideInLeft, {
params: {
delay: '0s',
duration: `${duration}ms`,
endOpacity: 1,
startOpacity: 1,
fromPosition: `${this.vertical ? 'translateY' : 'translateX'}(${this.currentItem.direction === 1 ? trans : -trans}%)`,
toPosition: `${this.vertical ? 'translateY(0%)' : 'translateX(0%)'}`
}
}),
leaveAnimation: useAnimation(slideInLeft, {
params: {
delay: '0s',
duration: `${duration}ms`,
endOpacity: 1,
startOpacity: 1,
fromPosition: `${this.vertical ? 'translateY(0%)' : 'translateX(0%)'}`,
toPosition: `${this.vertical ? 'translateY' : 'translateX'}(${this.currentItem.direction === 1 ? -trans : trans}%)`,
}
})
};
case CarouselAnimationType.fade:
return {
enterAnimation: useAnimation(fadeIn, { params: { duration: `${duration}ms`, startOpacity: `${this.animationPosition}` } }),
leaveAnimation: null
};
}
return {
enterAnimation: null,
leaveAnimation: null
};
}
playEnterAnimation() {
const animation = this.getAnimation().enterAnimation;
if (!animation) {
return;
}
this.enterAnimationPlayer = this.animationService.buildAnimation(animation, this.getCurrentElement());
this.enterAnimationPlayer.animationEnd.subscribe(() => {
// TODO: animation may never end. Find better way to clean up the player
if (this.enterAnimationPlayer) {
this.enterAnimationPlayer.destroy();
this.enterAnimationPlayer = null;
}
this.animationPosition = 0;
this.newDuration = 0;
this.previousItem.previous = false;
this.enterAnimationDone.emit();
this.cdr.markForCheck();
});
this.previousItem.previous = true;
this.enterAnimationPlayer.play();
}
playLeaveAnimation() {
const animation = this.getAnimation().leaveAnimation;
if (!animation) {
return;
}
this.leaveAnimationPlayer = this.animationService.buildAnimation(animation, this.getPreviousElement());
this.leaveAnimationPlayer.animationEnd.subscribe(() => {
// TODO: animation may never end. Find better way to clean up the player
if (this.leaveAnimationPlayer) {
this.leaveAnimationPlayer.destroy();
this.leaveAnimationPlayer = null;
}
this.animationPosition = 0;
this.newDuration = 0;
this.leaveAnimationDone.emit();
});
this.leaveAnimationPlayer.play();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCarouselComponentBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxCarouselComponentBase, isStandalone: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCarouselComponentBase, decorators: [{
type: Directive
}] });
class IgxCarouselIndicatorDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCarouselIndicatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxCarouselIndicatorDirective, isStandalone: true, selector: "[igxCarouselIndicator]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCarouselIndicatorDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxCarouselIndicator]',
standalone: true
}]
}] });
class IgxCarouselNextButtonDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCarouselNextButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxCarouselNextButtonDirective, isStandalone: true, selector: "[igxCarouselNextButton]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCarouselNextButtonDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxCarouselNextButton]',
standalone: true
}]
}] });
class IgxCarouselPrevButtonDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCarouselPrevButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxCarouselPrevButtonDirective, isStandalone: true, selector: "[igxCarouselPrevButton]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCarouselPrevButtonDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxCarouselPrevButton]',
standalone: true
}]
}] });
/**
* A slide component that usually holds an image and/or a caption text.
* IgxSlideComponent is usually a child component of an IgxCarouselComponent.
*
* ```
* <igx-slide [input bindings] >
* <ng-content></ng-content>
* </igx-slide>
* ```
*
* @export
*/
class IgxSlideComponent {
constructor() {
this.elementRef = inject(ElementRef);
/**
* Returns the `role` of the slide component.
* By default is set to `tabpanel`
*
* @memberof IgxSlideComponent
*/
this.tab = 'tabpanel';
/**
* Returns the class of the slide component.
* ```typescript
* let class = this.slide.cssClass;
* ```
*
* @memberof IgxSlideComponent
*/
this.cssClass = 'igx-slide';
this.previous = false;
/**
* @hidden
*/
this.activeChange = new EventEmitter();
this._active = false;
this._destroy$ = new Subject();
}
/**
* Returns the `tabIndex` of the slide component.
* ```typescript
* let tabIndex = this.carousel.tabIndex;
* ```
*
* @memberof IgxSlideComponent
* @deprecated in version 19.2.0.
*/
get tabIndex() {
return this.active ? 0 : null;
}
/**
* Gets/sets the `active` state of the slide.
* ```html
* <igx-carousel>
* <igx-slide [active] ="false"></igx-slide>
* <igx-carousel>
* ```
*
* Two-way data binding.
* ```html
* <igx-carousel>
* <igx-slide [(active)] ="model.isActive"></igx-slide>
* <igx-carousel>
* ```
*
* @memberof IgxSlideComponent
*/
get active() {
return this._active;
}
set active(value) {
this._active = value;
this.activeChange.emit(this._active);
}
/**
* Returns a reference to the carousel element in the DOM.
* ```typescript
* let nativeElement = this.slide.nativeElement;
* ```
*
* @memberof IgxSlideComponent
*/
get nativeElement() {
return this.elementRef.nativeElement;
}
/**
* @hidden
*/
get isDestroyed() {
return this._destroy$;
}
ngAfterContentChecked() {
this.id = `panel-${this.index}`;
this.ariaLabelledBy = `tab-${this.index}-${this.total}`;
}
/**
* @hidden
*/
ngOnDestroy() {
this._destroy$.next(true);
this._destroy$.complete();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSlideComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "21.0.2", type: IgxSlideComponent, isStandalone: true, selector: "igx-slide", inputs: { index: "index", direction: "direction", total: "total", active: ["active", "active", booleanAttribute], previous: ["previous", "previous", booleanAttribute] }, outputs: { activeChange: "activeChange" }, host: { properties: { "attr.tabindex": "this.tabIndex", "attr.id": "this.id", "attr.role": "this.tab", "attr.aria-labelledby": "this.ariaLabelledBy", "class.igx-slide": "this.cssClass", "class.igx-slide--current": "this.active", "class.igx-slide--previous": "this.previous" } }, ngImport: i0, template: "<ng-content></ng-content>\n" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSlideComponent, decorators: [{
type: Component,
args: [{ selector: 'igx-slide', standalone: true, template: "<ng-content></ng-content>\n" }]
}], propDecorators: { index: [{
type: Input
}], direction: [{
type: Input
}], total: [{
type: Input
}], tabIndex: [{
type: HostBinding,
args: ['attr.tabindex']
}], id: [{
type: HostBinding,
args: ['attr.id']
}], tab: [{
type: HostBinding,
args: ['attr.role']
}], ariaLabelledBy: [{
type: HostBinding,
args: ['attr.aria-labelledby']
}], cssClass: [{
type: HostBinding,
args: ['class.igx-slide']
}], active: [{
type: HostBinding,
args: ['class.igx-slide--current']
}, {
type: Input,
args: [{ transform: booleanAttribute }]
}], previous: [{
type: HostBinding,
args: ['class.igx-slide--previous']
}, {
type: Input,
args: [{ transform: booleanAttribute }]
}], activeChange: [{
type: Output
}] } });
let NEXT_ID = 0;
class CarouselHammerConfig extends HammerGestureConfig {
constructor() {
super(...arguments);
this.overrides = {
pan: { direction: HammerGesturesManager.Hammer?.DIRECTION_HORIZONTAL }
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: CarouselHammerConfig, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: CarouselHammerConfig }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: CarouselHammerConfig, decorators: [{
type: Injectable
}] });
/**
* **Ignite UI for Angular Carousel** -
* [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel.html)
*
* The Ignite UI Carousel is used to browse or navigate through a collection of slides. Slides can contain custom
* content such as images or cards and be used for things such as on-boarding tutorials or page-based interfaces.
* It can be used as a separate fullscreen element or inside another component.
*
* Example:
* ```html
* <igx-carousel>
* <igx-slide>
* <h3>First Slide Header</h3>
* <p>First slide Content</p>
* <igx-slide>
* <igx-slide>
* <h3>Second Slide Header</h3>
* <p>Second Slide Content</p>
* </igx-carousel>
* ```
*/
class IgxCarouselComponent extends IgxCarouselComponentBase {
/** @hidden */
get labelId() {
return this.showIndicatorsLabel ? `${this.id}-label` : null;
}
/** @hidden */
get isVertical() {
return this.vertical;
}
/**
* Gets the `touch-action` style of the `list item`.
* ```typescript
* let touchAction = this.listItem.touchAction;
* ```
*/
get touchAction() {
return this.gesturesSupport ? 'pan-y' : 'auto';
}
/**
* An accessor that sets the resource strings.
* By default it uses EN resources.
*/
set resourceStrings(value) {
this._resourceStrings = Object.assign({}, this._resourceStrings, value);
}
/**
* An accessor that returns the resource strings.
*/
get resourceStrings() {
return this._resourceStrings;
}
/** @hidden */
get getIndicatorTemplate() {
if (this.indicatorTemplate) {
return this.indicatorTemplate;
}
return this.defaultIndicator;
}
/** @hidden */
get getNextButtonTemplate() {
if (this.nextButtonTemplate) {
return this.nextButtonTemplate;
}
return this.defaultNextButton;
}
/** @hidden */
get getPrevButtonTemplate() {
if (this.prevButtonTemplate) {
return this.prevButtonTemplate;
}
return this.defaultPrevButton;
}
/** @hidden */
get indicatorsClass() {
return {
['igx-carousel-indicators--focused']: this._hasKeyboardFocusOnIndicators,
[`igx-carousel-indicators--${this.getIndicatorsClass()}`]: true
};
}
/** @hidden */
get showIndicators() {
return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
}
/** @hidden */
get showIndicatorsLabel() {
return this.indicators && this.total > this.maximumIndicatorsCount;
}
/** @hidden */
get getCarouselLabel() {
return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
}
/**
* Returns the total number of `slides` in the carousel.
* ```typescript
* let slideCount = this.carousel.total;
* ```
*
* @memberOf IgxCarouselComponent
*/
get total() {
return this.slides?.length;
}
/**
* The index of the slide being currently shown.
* ```typescript
* let currentSlideNumber = this.carousel.current;
* ```
*
* @memberOf IgxCarouselComponent
*/
get current() {
return !this.currentItem ? 0 : this.currentItem.index;
}
/**
* Returns a boolean indicating if the carousel is playing.
* ```typescript
* let isPlaying = this.carousel.isPlaying;
* ```
*
* @memberOf IgxCarouselComponent
*/
get isPlaying() {
return this.playing;
}
/**
* Returns а boolean indicating if the carousel is destroyed.
* ```typescript
* let isDestroyed = this.carousel.isDestroyed;
* ```
*
* @memberOf IgxCarouselComponent
*/
get isDestroyed() {
return this.destroyed;
}
/**
* Returns a reference to the carousel element in the DOM.
* ```typescript
* let nativeElement = this.carousel.nativeElement;
* ```
*
* @memberof IgxCarouselComponent
*/
get nativeElement() {
return this.element.nativeElement;
}
/**
* Returns the time `interval` in milliseconds before the slide changes.
* ```typescript
* let timeInterval = this.carousel.interval;
* ```
*
* @memberof IgxCarouselComponent
*/
get interval() {
return this._interval;
}
/**
* Sets the time `interval` in milliseconds before the slide changes.
* If not set, the carousel will not change `slides` automatically.
* ```html
* <igx-carousel [interval]="1000"></igx-carousel>
* ```
*
* @memberof IgxCarouselComponent
*/
set interval(value) {
this._interval = +value;
this.restartInterval();
}
constructor() {
super();
this.element = inject(ElementRef);
this.iterableDiffers = inject(IterableDiffers);
this.platformUtil = inject(PlatformUtil);
this.dir = inject(_IgxDirectionality);
this.document = inject(DOCUMENT);
/**
* Sets the `id` of the carousel.
* If not set, the `id` of the first carousel component will be `"igx-carousel-0"`.
* ```html
* <igx-carousel id="my-first-carousel"></igx-carousel>
* ```
*
* @memberof IgxCarouselComponent
*/
this.id = `igx-carousel-${NEXT_ID++}`;
/**
* Returns the `role` attribute of the carousel.
* ```typescript
* let carouselRole = this.carousel.role;
* ```
*
* @memberof IgxCarouselComponent
*/
this.role = 'region';
/** @hidden */
this.roleDescription = 'carousel';
/**
* Returns the class of the carousel component.
* ```typescript
* let class = this.carousel.cssClass;
* ```
*
* @memberof IgxCarouselComponent
*/
this.cssClass = 'igx-carousel';
/**
* Sets whether the carousel should `loop` back to the first slide after reaching the last slide.
* Default value is `true`.
* ```html
* <igx-carousel [loop]="false"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.loop = true;
/**
* Sets whether the carousel will `pause` the slide transitions on user interactions.
* Default value is `true`.
* ```html
* <igx-carousel [pause]="false"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.pause = true;
/**
* Controls whether the carousel should render the left/right `navigation` buttons.
* Default value is `true`.
* ```html
* <igx-carousel [navigation]="false"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.navigation = true;
/**
* Controls whether the carousel should render the indicators.
* Default value is `true`.
* ```html
* <igx-carousel [indicators]="false"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.indicators = true;
/**
* Controls whether the carousel has vertical alignment.
* Default value is `false`.
* ```html
* <igx-carousel [vertical]="true"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.vertical = false;
/**
* Controls whether the carousel should support gestures.
* Default value is `true`.
* ```html
* <igx-carousel [gesturesSupport]="false"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.gesturesSupport = true;
/**
* Controls the maximum indexes that can be shown.
* Default value is `10`.
* ```html
* <igx-carousel [maximumIndicatorsCount]="5"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.maximumIndicatorsCount = 10;
/**
* Gets/sets the display mode of carousel indicators. It can be `start` or `end`.
* Default value is `end`.
* ```html
* <igx-carousel indicatorsOrientation="start">
* <igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.indicatorsOrientation = CarouselIndicatorsOrientation.end;
/**
* Gets/sets the animation type of carousel.
* Default value is `slide`.
* ```html
* <igx-carousel animationType="none">
* <igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.animationType = CarouselAnimationType.slide;
/**
* The custom template, if any, that should be used when rendering carousel indicators
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.carousel.indicatorTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-carousel #carousel>
* ...
* <ng-template igxCarouselIndicator let-slide>
* <igx-icon *ngIf="slide.active">brightness_7</igx-icon>
* <igx-icon *ngIf="!slide.active">brightness_5</igx-icon>
* </ng-template>
* </igx-carousel>
* ```
*/
this.indicatorTemplate = null;
/**
* The custom template, if any, that should be used when rendering carousel next button
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.carousel.nextButtonTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-carousel #carousel>
* ...
* <ng-template igxCarouselNextButton let-disabled>
* <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
* <igx-icon name="add"></igx-icon>
* </button>
* </ng-template>
* </igx-carousel>
* ```
*/
this.nextButtonTemplate = null;
/**
* The custom template, if any, that should be used when rendering carousel previous button
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.carousel.prevButtonTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-carousel #carousel>
* ...
* <ng-template igxCarouselPrevButton let-disabled>
* <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
* <igx-icon name="remove"></igx-icon>
* </button>
* </ng-template>
* </igx-carousel>
* ```
*/
this.prevButtonTemplate = null;
/**
* An event that is emitted after a slide transition has happened.
* Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
* ```html
* <igx-carousel (slideChanged)="slideChanged($event)"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.slideChanged = new EventEmitter();
/**
* An event that is emitted after a slide has been added to the carousel.
* Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
* ```html
* <igx-carousel (slideAdded)="slideAdded($event)"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.slideAdded = new EventEmitter();
/**
* An event that is emitted after a slide has been removed from the carousel.
* Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
* ```html
* <igx-carousel (slideRemoved)="slideRemoved($event)"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.slideRemoved = new EventEmitter();
/**
* An event that is emitted after the carousel has been paused.
* Provides a reference to the `IgxCarouselComponent` as an event argument.
* ```html
* <igx-carousel (carouselPaused)="carouselPaused($event)"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.carouselPaused = new EventEmitter();
/**
* An event that is emitted after the carousel has resumed transitioning between `slides`.
* Provides a reference to the `IgxCarouselComponent` as an event argument.
* ```html
* <igx-carousel (carouselPlaying)="carouselPlaying($event)"></igx-carousel>
* ```
*
* @memberOf IgxCarouselComponent
*/
this.carouselPlaying = new EventEmitter();
this._resourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN);
this.destroy$ = new Subject();
this.differ = null;
this._hasKeyboardFocusOnIndicators = false;
this.differ = this.iterableDiffers.find([]).create(null);
}
/** @hidden */
onTap(event) {
// play pause only when tap on slide
if (event.target && event.target.classList.contains('igx-slide')) {
if (this.isPlaying) {
if (this.pause) {
this.stoppedByInteraction = true;
}
this.stop();
}
else if (this.stoppedByInteraction) {
this.play();
}
}
}
/** @hidden */
onMouseEnter() {
if (this.pause && this.isPlaying) {
this.stoppedByInteraction = true;
}
this.stop();
}
/** @hidden */
onMouseLeave() {
if (this.stoppedByInteraction) {
this.play();
}
}
/** @hidden */
onPanLeft(event) {
if (!this.vertical) {
this.pan(event);
}
}
/** @hidden */
onPanRight(event) {
if (!this.vertical) {
this.pan(event);
}
}
/** @hidden */
onPanUp(event) {
if (this.vertical) {
this.pan(event);
}
}
/** @hidden */
onPanDown(event) {
if (this.vertical) {
this.pan(event);
}
}
/**
* @hidden
*/
onPanEnd(event) {
if (!this.gesturesSupport) {
return;
}
event.preventDefault();
const slideSize = this.vertical
? this.currentItem.nativeElement.offsetHeight
: this.currentItem.nativeElement.offsetWidth;
const panOffset = (slideSize / 1000);
const eventDelta = this.vertical ? event.deltaY : event.deltaX;
const delta = Math.abs(eventDelta) + panOffset < slideSize ? Math.abs(eventDelta) : slideSize - panOffset;
const velocity = Math.abs(event.velocity);
this.resetSlideStyles(this.currentItem);
if (this.incomingSlide) {
this.resetSlideStyles(this.incomingSlide);
if (slideSize / 2 < delta || velocity > 1) {
this.incomingSlide.direction = eventDelta < 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
this.incomingSlide.previous = false;
this.animationPosition = this.animationType === CarouselAnimationType.fade ?
delta / slideSize : (slideSize - delta) / slideSize;
if (velocity > 1) {
this.newDuration = this.defaultAnimationDuration / velocity;
}
this.incomingSlide.active = true;
}
else {
this.currentItem.direction = eventDelta > 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
this.previousItem = this.incomingSlide;
this.previousItem.previous = true;
this.animationPosition = this.animationType === CarouselAnimationType.fade ?
Math.abs((slideSize - delta) / slideSize) : delta / slideSize;
this.playAnimations();
}
}
if (this.stoppedByInteraction) {
this.play();
}
}
/** @hidden */
ngAfterContentInit() {
this.slides.changes
.pipe(takeUntil(this.destroy$))
.subscribe((change) => this.initSlides(change));
this.initSlides(this.slides);
}
/** @hidden */
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next(true);
this.destroy$.complete();
this.destroyed = true;
if (this.lastInterval) {
clearInterval(this.lastInterval);
}
}
/** @hidden */
handleKeydownPrev(event) {
if (this.platformUtil.isActivationKey(event)) {
event.preventDefault();
this.prev();
}
}
/** @hidden */
handleKeydownNext(event) {
if (this.platformUtil.isActivationKey(event)) {
event.preventDefault();
this.next();
}
}
/** @hidden */
handleKeyUp(event) {
if (event.key === this.platformUtil.KEYMAP.TAB) {
this._hasKeyboardFocusOnIndicators = true;
}
}
/** @hidden */
handleFocusOut(event) {
const target = event.relatedTarget;
if (!target || !target.classList.contains('igx-carousel-indicators__indicator')) {
this._hasKeyboardFocusOnIndicators = false;
}
}
/** @hidden */
handleClick() {
this._hasKeyboardFocusOnIndicators = false;
}
/** @hidden */
handleKeydown(event) {
const { key } = event;
const slides = this.slides.toArray();
switch (key) {
case this.platformUtil.KEYMAP.ARROW_LEFT:
this.dir.rtl ? this.next() : this.prev();
break;
case this.platformUtil.KEYMAP.ARROW_RIGHT:
this.dir.rtl ? this.prev() : this.next();
break;
case this.platformUtil.KEYMAP.HOME:
event.preventDefault();
this.select(this.dir.rtl ? last(slides) : first(slides));
break;
case this.platformUtil.KEYMAP.END:
event.preventDefault();
this.select(this.dir.rtl ? first(slides) : last(slides));
break;
}
this.indicatorsElements[this.current].nativeElement.focus();
}
/**
* Returns the slide corresponding to the provided `index` or null.
* ```typescript
* let slide1 = this.carousel.get(1);
* ```
*
* @memberOf IgxCarouselComponent
*/
get(index) {
return this.slides.find((slide) => slide.index === index);
}
/**
* Adds a new slide to the carousel.
* ```typescript
* this.carousel.add(newSlide);
* ```
*
* @memberOf IgxCarouselComponent
*/
add(slide) {
const newSlides = this.slides.toArray();
newSlides.push(slide);
this.slides.reset(newSlides);
this.slides.notifyOnChanges();
}
/**
* Removes a slide from the carousel.
* ```typescript
* this.carousel.remove(slide);
* ```
*
* @memberOf IgxCarouselComponent
*/
remove(slide) {
if (slide && slide === this.get(slide.index)) { // check if the requested slide for delete is present in the carousel
const newSlides = this.slides.toArray();
newSlides.splice(slide.index, 1);
this.slides.reset(newSlides);
this.slides.notifyOnChanges();
}
}
select(slideOrIndex, direction = CarouselAnimationDirection.NONE) {
const slide = typeof slideOrIndex === 'number'
? this.get(slideOrIndex)
: slideOrIndex;
if (slide && slide !== this.currentItem) {
slide.direction = direction;
slide.active = true;
}
}
/**
* Transitions to the next slide in the carousel.
* ```typescript
* this.carousel.next();
* ```
*
* @memberOf IgxCarouselComponent
*/
next() {
const index = this.getNextIndex();
if (index === 0 && !this.loop) {
this.stop();
return;
}
return this.select(this.get(index), CarouselAnimationDirection.NEXT);
}
/**
* Transitions to the previous slide in the carousel.
* ```typescript
* this.carousel.prev();
* ```
*
* @memberOf IgxCarouselComponent
*/
prev() {
const index = this.getPrevIndex();
if (!this.loop && index === this.total - 1) {
this.stop();
return;
}
return this.select(this.get(index), CarouselAnimationDirection.PREV);
}
/**
* Resumes playing of the carousel if in paused state.
* No operation otherwise.
* ```typescript
* this.carousel.play();
* }
* ```
*
* @memberOf IgxCarouselComponent
*/
play() {
if (!this.playing) {
this.playing = true;
this.carouselPlaying.emit(this);
this.restartInterval();
this.stoppedByInteraction = false;
}
}
/**
* Stops slide transitions if the `pause` option is set to `true`.
* No operation otherwise.
* ```typescript
* this.carousel.stop();
* }
* ```
*
* @memberOf IgxCarouselComponent
*/
stop() {
if (this.pause) {
this.playing = false;
this.carouselPaused.emit(this);
this.resetInterval();
}
}
getPreviousElement() {
return this.previousItem.nativeElement;
}
getCurrentElement() {
return this.currentItem.nativeElement;
}
resetInterval() {
if (this.lastInterval) {
clearInterval(this.lastInterval);
this.lastInterval = null;
}
}
restartInterval() {
this.resetInterval();
if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
this.lastInterval = setInterval(() => {
const tick = +this.interval;
if (this.playing && this.total && !isNaN(tick) && tick > 0) {
this.next();
}
else {
this.stop();
}
}, this.interval);
}
}
/** @hidden */
get nextButtonDisabled() {
return !this.loop && this.current === (this.total - 1);
}
/** @hidden */
get prevButtonDisabled() {
return !this.loop && this.current === 0;
}
get indicatorsElements() {
return this._indicators.toArray();
}
getIndicatorsClass() {
switch (this.indicatorsOrientation) {
case CarouselIndicatorsOrientation.top:
return CarouselIndicatorsOrientation.start;
case CarouselIndicatorsOrientation.bottom:
return CarouselIndicatorsOrientation.end;
default:
return this.indicatorsOrientation;
}
}
getNextIndex() {
return (this.current + 1) % this.total;
}
getPrevIndex() {
return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
}
resetSlideStyles(slide) {
slide.nativeElement.style.transform = '';
slide.nativeElement.style.opacity = '';
}
pan(event) {
const slideSize = this.vertical
? this.currentItem.nativeElement.offsetHeight
: this.currentItem.nativeElement.offsetWidth;
const panOffset = (slideSize / 1000);
const delta = this.vertical ? event.deltaY : event.deltaX;
const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
return;
}
if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
this.incomingSlide = null;
return;
}
event.preventDefault();
if (this.isPlaying) {
this.stoppedByInteraction = true;
this.stop();
}
if (this.previousItem && this.previousItem.previous) {
this.previousItem.previous = false;
}
this.finishAnimations();
if (this.incomingSlide) {
if (index !== this.incomingSlide.index) {
this.resetSlideStyles(this.incomingSlide);
this.incomingSlide.previous = false;
this.incomingSlide = this.get(index);
}
}
else {
this.incomingSlide = this.get(index);
}
this.incomingSlide.previous = true;
if (this.animationType === CarouselAnimationType.fade) {
this.currentItem.nativeElement.style.opacity = `${Math.abs(offset) / slideSize}`;
}
else {
this.currentItem.nativeElement.style.transform = this.vertical
? `translateY(${delta}px)`
: `translateX(${delta}px)`;
this.incomingSlide.nativeElement.style.transform = this.vertical
? `translateY(${offset}px)`
: `translateX(${offset}px)`;
}
}
unsubscriber(slide) {
return merge(this.destroy$, slide.isDestroyed);
}
onSlideActivated(slide) {
if (slide.active && slide !== this.currentItem) {
if (slide.direction === CarouselAnimationDirection.NONE) {
const newIndex = slide.index;
slide.direction = newIndex > this.current ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
}
if (this.currentItem) {
if (this.previousItem && this.previousItem.previous) {
this.previousItem.previous = false;
}
this.currentItem.direction = slide.direction;
this.currentItem.active = false;
this.previousItem = this.currentItem;
this.currentItem = slide;
this.triggerAnimations();
}
else {
this.currentItem = slide;
}
this.slideChanged.emit({ carousel: this, slide });
this.restartInterval();
this.cdr.markForCheck();
}
}
finishAnimations() {
if (this.animationStarted(this.leaveAnimationPlayer)) {
this.leaveAnimationPlayer.finish();
}
if (this.animationStarted(this.enterAnimationPlayer)) {
this.enterAnimationPlayer.finish();
}
}
initSlides(change) {
const diff = this.differ.diff(change.toArray());
if (diff) {
this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
diff.forEachAddedItem((record) => {
const slide = record.item;
slide.total = this.total;
this.slideAdded.emit({ carousel: this, slide });
if (slide.active) {
this.currentItem = slide;
}
slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
});
diff.forEachRemovedItem((record) => {
const slide = record.item;
this.slideRemoved.emit({ carousel: this, slide });
if (slide.active) {
slide.active = false;
this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
}
});
this.updateSlidesSelection();
}
}
updateSlidesSelection() {
if (this.platformUtil.isBrowser) {
requestAnimationFrame(() => {
if (this.currentItem) {
this.currentItem.active = true;
const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
activeSlides.forEach(slide => slide.active = false);
}
else if (this.total) {
this.slides.first.active = true;
}
this.play();
});
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCarouselComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.2", type: IgxCarouselComponent, isStandalone: true, selector: "igx-carousel", inputs: { id: "id", loop: ["loop", "loop", booleanAttribute], pause: ["pause", "pause", booleanAttribute], navigation: ["navigation", "navigation", booleanAttribute], indicators: ["indicators", "indicators", booleanAttribute], vertical: ["vertical", "vertical", booleanAttribute], gesturesSupport: ["gesturesSupport", "gesturesSupport", booleanAttribute], maximumIndicatorsCount: "maximumIndicatorsCount", indicatorsOrientation: "indicatorsOrientation", animationType: "animationType", resourceStrings: "resourceStrings", interval: "interval" }, outputs: { slideChanged: "slideChanged", slideAdded: "slideAdded", slideRemoved: "slideRemoved", carouselPaused: "carouselPaused", carouselPlaying: "carouselPlaying" }, host: { listeners: { "tap": "onTap($event)", "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()", "panleft": "onPanLeft($event)", "panright": "onPanRight($event)", "panup": "onPanUp($event)", "pandown": "onPanDown($event)", "panend": "onPanEnd($event)" }, properties: { "attr.id": "this.id", "attr.role": "this.role", "attr.aria-roledescription": "this.roleDescription", "attr.aria-labelledby": "this.labelId", "class.igx-carousel--vertical": "this.isVertical", "class.igx-carousel": "this.cssClass", "style.touch-action": "this.touchAction" } }, providers: [
{
provide: HAMMER_GESTURE_CONFIG,
useClass: CarouselHammerConfig
}
], queries: [{ propertyName: "indicatorTemplate", first: true, predicate: IgxCarouselIndicatorDirective, descendants: true, read: TemplateRef }, { propertyName: "nextButtonTemplate", first: true, predicate: IgxCarouselNextButtonDirective, descendants: true, read: TemplateRef }, { propertyName: "prevButtonTemplate", first: true, predicate: IgxCarouselPrevButtonDirective, descendants: true, read: TemplateRef }, { propertyName: "slides", predicate: IgxSlideComponent }], viewQueries: [{ propertyName: "defaultIndicator", first: true, predicate: ["defaultIndicator"], descendants: true, read: TemplateRef, static: true }, { propertyName: "defaultNextButton", first: true, predicate: ["defaultNextButton"], descendants: true, read: TemplateRef, static: true }, { propertyName: "defaultPrevButton", first: true, predicate: ["defaultPrevButton"], descendants: true, read: TemplateRef, static: true }, { propertyName: "_indicators", predicate: ["indicators"], descendants: true, read: ElementRef }], usesInheritance: true, ngImport: i0, template: "<ng-template #defaultIndicator let-slide>\n <div class=\"igx-nav-dot\"\n [class.igx-nav-dot--active]=\"slide.active\">\n </div>\n</ng-template>\n\n<ng-template #defaultNextButton>\n <igx-icon aria-hidden=\"true\" family=\"default\" name=\"carousel_next\"\n class=\"igx-nav-arrow\">\n </igx-icon>\n</ng-template>\n\n<ng-template #defaultPrevButton>\n <igx-icon aria-hidden=\"true\" family=\"default\" name=\"carousel_prev\"\n class=\"igx-nav-arrow\">\n </igx-icon>\n</ng-template>\n\n@if (navigation && slides.length) {\n <button\n igxButton\n class=\"igx-carousel__arrow--prev\"\n [attr.aria-label]=\"resourceStrings.igx_carousel_previous_slide\"\n [disabled]=\"prevButtonDisabled\"\n (click)=\"prev()\"\n (keydown)=\"handleKeydownPrev($event)\">\n <ng-container *ngTemplateOutlet=\"getPrevButtonTemplate; context: {$implicit: prevButtonDisabled};\"></ng-container>\n </button>\n}\n\n@if (navigation && slides.length) {\n <button\n igxButton\n class=\"igx-carousel__arrow--next\"\n [attr.aria-label]=\"resourceStrings.igx_carousel_next_slide\"\n [disabled]=\"nextButtonDisabled\"\n (click)=\"next()\"\n (keydown)=\"handleKeydownNext($event)\">\n <ng-container *ngTemplateOutlet=\"getNextButtonTemplate; context: {$implicit: nextButtonDisabled};\"></ng-container>\n </button>\n}\n\n@if (showIndicators) {\n <div [ngClass]=\"indicatorsClass\" [attr.role]=\"'tablist'\" (keyup)=\"handleKeyUp($event)\" (focusout)=\"handleFocusOut($event)\" (click)=\"handleClick()\" (keydown)=\"handleKeydown($event)\">\n @for (slide of slides; track slide) {\n <div #indicators\n class=\"igx-carousel-indicators__indicator\"\n (click)=\"select(slide)\"\n [id]=\"'tab-'+ slide.index + '-' + total\"\n [attr.role]=\"'tab'\"\n