UNPKG

@ngfly/carousel

Version:

A smooth, customizable carousel component for Angular 17+ applications

1 lines 33.2 kB
import{Component as t,Input as e,ContentChild as i,ViewChild as n,ChangeDetectorRef as s,NgZone as o,inject as r}from"@angular/core";import{CommonModule as a}from"@angular/common";import{Subject as l,fromEvent as c}from"rxjs";import{takeUntil as h,debounceTime as d}from"rxjs/operators";import{CarouselService as u,CAROUSEL_DEFAULTS as m}from"../../services/carousel.service";import{createTranslation as g}from"../../utils/animation";import*as p from"@angular/core";import*as f from"@angular/common";export class CarouselComponent{constructor(){this.cdr=r(s),this.ngZone=r(o),this.carouselService=r(u),this.slides=[],this.configs={},this.currentTranslate=0,this.currentIndex=0,this.destroy$=new l,this.itemWidths=[],this.itemHeights=[],this.containerWidth=0,this.containerHeight=0,this.intersectionObserver=null,this.resizeObserver=null,this.visibilityChangeTimeout=null,this.initialized=!1,this.scrollSizeMap={xs:50,sm:100,md:150,lg:200,xl:250,"2xl":300,"3xl":350,"4xl":400,"5xl":450,"6xl":500,"7xl":550,"8xl":600,"9xl":650,"10xl":700,full:1},this.showPrevButton=!1,this.showNextButton=!1,this.filteredItems=[],this.emptyStateText="No items found",this.emptyStateIcon="📭",this.emptyStateTextColor="#666",this.showEmptyStateIcon=!0}ngOnInit(){this.filteredItems=this.slides||[];this.filteredItems.length>1&&(this.showPrevButton=!0,this.showNextButton=!0),this.configs.emptyState&&(this.emptyStateText=this.configs.emptyState.text||this.emptyStateText,this.emptyStateIcon=this.configs.emptyState.icon||this.emptyStateIcon,this.emptyStateTextColor=this.configs.emptyState.textColor||this.emptyStateTextColor,this.showEmptyStateIcon=!this.configs.emptyState.hideIcon)}ngOnChanges(t){t.slides&&(this.filteredItems=this.slides||[],this.initialized&&setTimeout((()=>{this.updateContainerDimensions(),this.calculateItemDimensions(),this.checkOverflow(),this.cdr.detectChanges()}))),t.configs&&this.initialized&&setTimeout((()=>{this.updateContainerDimensions(),this.calculateItemDimensions(),this.checkOverflow(),this.setupAutoplay(),this.cdr.detectChanges()}))}setupAutoplay(){if(this.clearAutoplayInterval(),!this.configs.autoplay)return;const t=this.carouselService.parseTimeToMs(this.configs.autoplayDelay||"3000ms");this.autoplayInterval=setInterval((()=>{const t=this.trackElement?.nativeElement,e=this.wrapperElement?.nativeElement;if(!t||!e)return;const i=this.isVertical?t.offsetHeight-e.offsetHeight:t.offsetWidth-e.offsetWidth;if(this.currentTranslate>=i){if(!this.configs.loop)return void this.clearAutoplayInterval();this.currentTranslate=0,this.currentIndex=0}else this.next();this.cdr.detectChanges()}),t)}clearAutoplayInterval(){this.autoplayInterval&&(clearInterval(this.autoplayInterval),this.autoplayInterval=void 0)}ngAfterViewInit(){this.ngZone.runOutsideAngular((()=>{setTimeout((()=>{this.ngZone.run((()=>{this.initializeCarousel(),this.setupResizeListener(),this.setupIntersectionObserver(),this.setupResizeObserver(),this.updateContainerDimensions(),this.calculateItemDimensions(),this.checkOverflow(),this.initialized=!0,this.cdr.detectChanges(),setTimeout((()=>{this.setupAutoplay()})),setTimeout((()=>{this.updateContainerDimensions(),this.calculateItemDimensions(),this.checkOverflow(),this.cdr.detectChanges()}),100)}))}))}))}ngOnDestroy(){this.autoplayInterval&&(clearInterval(this.autoplayInterval),this.autoplayInterval=void 0),this.intersectionObserver&&(this.intersectionObserver.disconnect(),this.intersectionObserver=null),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.visibilityChangeTimeout&&(clearTimeout(this.visibilityChangeTimeout),this.visibilityChangeTimeout=null),this.destroy$.next(),this.destroy$.complete(),this.itemWidths=[],this.itemHeights=[],this.filteredItems=[]}get isVertical(){return"vertical"===this.configs.orientation}get containerStyles(){const t=this.configs.containerStyle||{};return"width"in t||(t.width=this.configs.containerWidth||"100%"),"height"in t||(t.height=this.configs.containerHeight||"auto"),t}get trackStyles(){const t={transform:g(this.currentTranslate,this.isVertical)};return this.isVertical?{...t,flexDirection:"column"}:t}initializeCarousel(){this.trackElement&&this.wrapperElement&&(this.currentTranslate=0,this.currentIndex=0,this.checkOverflow())}setupResizeListener(){c(window,"resize").pipe(d(200),h(this.destroy$)).subscribe((()=>{this.updateContainerDimensions(),this.calculateItemDimensions(),this.checkOverflow()}))}setupIntersectionObserver(){"IntersectionObserver"in window&&this.ngZone.runOutsideAngular((()=>{this.intersectionObserver=new IntersectionObserver((t=>{const e=t[0]?.isIntersecting;e&&this.initialized&&this.ngZone.run((()=>{this.visibilityChangeTimeout=setTimeout((()=>{this.updateContainerDimensions(),this.calculateItemDimensions(),this.checkOverflow()}),50)}))}),{threshold:.1}),this.wrapperElement?.nativeElement&&this.intersectionObserver.observe(this.wrapperElement.nativeElement)}))}setupResizeObserver(){"ResizeObserver"in window&&this.ngZone.runOutsideAngular((()=>{this.resizeObserver=new ResizeObserver((()=>{this.initialized&&this.ngZone.run((()=>{this.updateContainerDimensions(),this.calculateItemDimensions(),this.checkOverflow()}))})),this.wrapperElement?.nativeElement&&this.resizeObserver.observe(this.wrapperElement.nativeElement)}))}updateContainerDimensions(){this.wrapperElement&&(this.containerWidth=this.wrapperElement.nativeElement.offsetWidth,this.containerHeight=this.wrapperElement.nativeElement.offsetHeight)}checkOverflow(){if(!this.initialized&&!this.trackElement?.nativeElement)return;if(this.filteredItems.length>1&&this.configs.singleItemMode)return this.configs.loop?(this.showPrevButton=!0,void(this.showNextButton=!0)):(this.showPrevButton=this.currentIndex>0,void(this.showNextButton=this.currentIndex<this.filteredItems.length-1));if(!this.showNavigation||this.filteredItems.length<=1)return this.showPrevButton=!1,void(this.showNextButton=!1);const t=this.trackElement?.nativeElement,e=this.wrapperElement?.nativeElement;if(t&&e&&0!==e.offsetWidth&&0!==e.offsetHeight){if(this.configs.loop&&this.filteredItems.length>1)this.showPrevButton=!0,this.showNextButton=!0;else if(this.showPrevButton=this.currentTranslate>0,this.isVertical){const i=t.offsetHeight,n=e.offsetHeight;this.showNextButton=i-this.currentTranslate>n+1}else{const i=t.offsetWidth,n=e.offsetWidth;this.showNextButton=i-this.currentTranslate>n+1}this.cdr.detectChanges()}}calculateItemDimensions(){if(!this.trackElement||!this.wrapperElement)return;const t=this.wrapperElement.nativeElement,e=t.offsetHeight,i=t.offsetWidth;this.containerWidth=i,this.containerHeight=e;const n=Array.from(this.trackElement.nativeElement.children);0!==n.length&&setTimeout((()=>{if(this.configs.singleItemMode)return this.isVertical?(this.itemHeights=n.map((()=>e)),this.itemWidths=n.map((t=>{const e=window.getComputedStyle(t);return t.offsetWidth+parseInt(e.marginLeft||"0",10)+parseInt(e.marginRight||"0",10)}))):(this.itemWidths=n.map((()=>i)),this.itemHeights=n.map((t=>{const e=window.getComputedStyle(t);return t.offsetHeight+parseInt(e.marginTop||"0",10)+parseInt(e.marginBottom||"0",10)}))),this.updateTranslatePosition(),void this.cdr.detectChanges();this.itemWidths=n.map((t=>{const e=window.getComputedStyle(t);return t.offsetWidth+parseInt(e.marginLeft||"0",10)+parseInt(e.marginRight||"0",10)})),this.itemHeights=n.map((t=>{const e=window.getComputedStyle(t);return t.offsetHeight+parseInt(e.marginTop||"0",10)+parseInt(e.marginBottom||"0",10)})),this.cdr.detectChanges()}),50)}getScrollAmount(){const t=this.wrapperElement?.nativeElement;if(!t)return 0;if(this.configs.singleItemMode){if(this.isVertical&&this.itemHeights.length>0)return this.currentIndex<this.itemHeights.length?this.itemHeights[this.currentIndex]:this.itemHeights[0]||t.offsetHeight;if(this.itemWidths.length>0)return this.currentIndex<this.itemWidths.length?this.itemWidths[this.currentIndex]:this.itemWidths[0]||t.offsetWidth}const e=this.configs.scrollSize||"sm";if("full"===e)return this.isVertical?t.offsetHeight:t.offsetWidth;if("string"==typeof e&&e.endsWith("%")){const i=parseInt(e,10)/100;return this.isVertical?t.offsetHeight*i:t.offsetWidth*i}return this.scrollSizeMap[e]||this.scrollSizeMap.sm}previous(){if(this.showPrevButton){if(this.configs.singleItemMode)this.currentIndex>0?(this.currentIndex--,this.updateTranslatePosition()):this.configs.loop&&this.filteredItems.length>0&&(this.currentIndex=this.filteredItems.length-1,this.updateTranslatePosition());else{const t=this.getScrollAmount();this.currentTranslate=Math.max(0,this.currentTranslate-t)}this.checkOverflow()}}next(){if(!this.showNextButton)return;const t=this.trackElement?.nativeElement,e=this.wrapperElement?.nativeElement;if(t&&e){if(this.configs.singleItemMode)this.currentIndex<this.filteredItems.length-1?(this.currentIndex++,this.updateTranslatePosition()):this.configs.loop&&this.filteredItems.length>0&&(this.currentIndex=0,this.currentTranslate=0);else{const i=this.getScrollAmount(),n=this.isVertical?t.offsetHeight-e.offsetHeight:t.offsetWidth-e.offsetWidth;this.currentTranslate=Math.min(n,this.currentTranslate+i)}this.checkOverflow()}}updateTranslatePosition(){if(0===this.currentIndex)return void(this.currentTranslate=0);const t=this.configs.itemGap?parseInt(this.configs.itemGap.replace("px",""),10):0;let e=0;for(let i=0;i<this.currentIndex;i++)this.isVertical?e+=(this.itemHeights[i]||0)+t:e+=(this.itemWidths[i]||0)+t;this.currentTranslate=e}getItemStyle(t){const e={flexShrink:"0",flexGrow:"0",boxSizing:"border-box",overflow:"hidden",borderRadius:"inherit"};if(this.configs.itemWidth&&("100%"===this.configs.itemWidth&&this.containerWidth>0?(e.width=this.containerWidth+"px",e.maxWidth="100%"):e.width=this.configs.itemWidth),this.configs.itemHeight?("100%"===this.configs.itemHeight||parseInt(this.configs.itemHeight,10)===this.containerHeight)&&this.containerHeight>0?(e.height=this.containerHeight+"px",e.minHeight=this.containerHeight+"px",e.maxHeight="100%",e.display="flex",e.flexDirection="column",e.alignItems="stretch",e.justifyContent="stretch"):e.height=this.configs.itemHeight:this.isVertical&&this.configs.containerHeight&&(e.height=this.containerHeight+"px",e.minHeight=this.containerHeight+"px"),!this.configs.itemGap)return e;return e[this.isVertical?"marginTop":"marginLeft"]=0===t?"0":this.configs.itemGap,e}get contentPadding(){return this.configs.contentPadding||m.CONTENT_PADDING}get animationDuration(){return this.configs.animationDuration||m.ANIMATION_DURATION}get animationTiming(){return this.configs.animationTiming||m.ANIMATION_TIMING}get showNavigation(){return this.configs.showNavigation??!0}getEmptyStateContainerStyle(){return{width:"100%",boxSizing:"border-box",borderRadius:"inherit",backgroundColor:this.configs.emptyState?.backgroundColor||"transparent"}}hasItems(){return this.filteredItems?.length>0}getNavControlsClass(){return"carousel__nav-controls"}getNavControlsStyle(){const t={position:"absolute",top:"0",left:"0",width:"100%",height:"100%",pointerEvents:"none"};return this.configs.navigationStyle?.zIndex&&(t["--carousel-z-index"]=this.configs.navigationStyle.zIndex),t}get prevIcon(){return this.carouselService.getNavigationIcons(this.isVertical,this.configs.navigationStyle?.icons).prev}get nextIcon(){return this.carouselService.getNavigationIcons(this.isVertical,this.configs.navigationStyle?.icons).next}getIconStyles(t){return{color:t?this.configs.navigationStyle?.nextButton?.color||"#666":this.configs.navigationStyle?.prevButton?.color||"#666"}}getButtonPositionStyle(t){const e=this.configs.navigationStyle,i="prev"===t?e?.prevButton||{}:e?.nextButton||{},n={position:"absolute",pointerEvents:"auto"};["top","bottom","left","right"].forEach((t=>{const e=i[t];void 0!==e&&(n[t]=0===e||"0"===e?"0px":e)}));const s={"left=50%":"translateX(-50%)","right=50%":"translateX(50%)","top=50%":"translateY(-50%)","bottom=50%":"translateY(50%)"};for(const[t,e]of Object.entries(s)){const[s,o]=t.split("=");if(i[s]===o){n.transform=i.transform?`${i.transform} ${e}`:e;break}}!n.transform&&i.transform&&(n.transform=i.transform);return["top","bottom","left","right"].some((t=>void 0!==i[t]))||(this.isVertical?(n.left="50%",n.transform="translateX(-50%)",n["prev"===t?"top":"bottom"]="0px"):(n.top="50%",n.transform="translateY(-50%)",n["prev"===t?"left":"right"]="0px")),n}getButtonFullStyles(t){const e={...this.carouselService.getButtonShapeStyles(this.configs.navigationStyle?.buttonShape)},i=this.configs.navigationStyle?.[`${t}Button`];if(i){const n=this.getButtonPositionStyle(t);Object.assign(e,n);const s=this.configs.navigationStyle?.buttonShape;if(s){const{borderRadius:t,...n}=i;Object.assign(e,n)}else Object.assign(e,i);i.zIndex?e["--carousel-z-index"]=i.zIndex:this.configs.navigationStyle?.zIndex&&(e["--carousel-z-index"]=this.configs.navigationStyle.zIndex)}return e}getPrevButtonFullStyles(){return this.getButtonFullStyles("prev")}getNextButtonFullStyles(){return this.getButtonFullStyles("next")}getPrevIndex(t){const e=this.filteredItems?.length||0;return 0===e?t:t>0?t-1:this.configs.loop?e-1:t}goToPrevSlide(){const t=this.getPrevIndex(this.currentIndex);t!==this.currentIndex&&(this.currentIndex=t,this.updateTranslatePosition(),this.checkOverflow())}getIndicatorContainerStyles(){return this.carouselService.getIndicatorContainerStyles(this.configs.indicatorStyle)}getIndicatorItemStyles(t){const e=t===this.currentIndex;return this.carouselService.getIndicatorStyles(this.configs.indicatorStyle,e)}goToSlide(t){if(t===this.currentIndex||t<0||t>=this.filteredItems.length)return;if(this.currentIndex=t,this.configs.singleItemMode)return this.updateTranslatePosition(),void this.checkOverflow();const e=this.trackElement?.nativeElement,i=this.wrapperElement?.nativeElement;if(!e||!i)return;const n=parseInt(this.configs.itemGap?.replace("px","")||"0",10),s=(this.isVertical?this.itemHeights:this.itemWidths).slice(0,t).reduce(((t,e)=>t+(e||0)+n),0),o=this.isVertical?e.offsetHeight-i.offsetHeight:e.offsetWidth-i.offsetWidth;this.currentTranslate=Math.min(o,Math.max(0,s)),this.checkOverflow()}get showIndicators(){return this.configs.showIndicators??!1}}CarouselComponent.ɵfac=p.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"14.3.0",ngImport:p,type:CarouselComponent,deps:[],target:p.ɵɵFactoryTarget.Component}),CarouselComponent.ɵcmp=p.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"14.3.0",type:CarouselComponent,isStandalone:!0,selector:"carousel",inputs:{slides:"slides",configs:"configs"},providers:[u],queries:[{propertyName:"itemTemplate",first:!0,predicate:["carouselItem"],descendants:!0},{propertyName:"emptyStateTemplate",first:!0,predicate:["emptyState"],descendants:!0}],viewQueries:[{propertyName:"trackElement",first:!0,predicate:["track"],descendants:!0},{propertyName:"wrapperElement",first:!0,predicate:["wrapper"],descendants:!0}],usesOnChanges:!0,ngImport:p,template:'<div class="carousel" \n [class.carousel--vertical]="isVertical" \n [ngClass]="configs.containerClass" \n [ngStyle]="containerStyles"\n [style.height]="configs.containerHeight || \'auto\'"\n [style.width]="configs.containerWidth || \'auto\'">\n <div\n #wrapper\n [style.--content-padding]="contentPadding"\n class="carousel__wrapper">\n <div\n #track\n [ngStyle]="trackStyles"\n [class.carousel__track--vertical]="isVertical"\n [style.--animation-duration]="animationDuration"\n [style.--animation-timing]="animationTiming"\n class="carousel__track">\n <ng-container *ngIf="hasItems(); else emptyState">\n <ng-container *ngFor="let slide of filteredItems; let i = index">\n <div class="carousel__item" \n [ngClass]="configs.itemClass" \n [ngStyle]="getItemStyle(i)" \n [style.--item-width]="configs.itemWidth || \'auto\'"\n [style.--item-height]="configs.itemHeight || \'auto\'">\n <ng-container *ngIf="itemTemplate; else defaultTemplate">\n <ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: slide, index: i }"></ng-container>\n </ng-container>\n <ng-template #defaultTemplate>\n <div class="carousel__item-default">\n {{ slide }}\n </div>\n </ng-template>\n </div>\n </ng-container>\n </ng-container>\n \n <ng-template #emptyState>\n <div class="carousel__empty-container" [ngStyle]="getEmptyStateContainerStyle()">\n <ng-container *ngIf="emptyStateTemplate; else defaultEmptyState">\n <ng-container *ngTemplateOutlet="emptyStateTemplate; context: { $implicit: emptyStateText }"></ng-container>\n </ng-container>\n <ng-template #defaultEmptyState>\n <div class="carousel__empty-state" [style.color]="emptyStateTextColor">\n <div *ngIf="showEmptyStateIcon" class="carousel__empty-icon">{{emptyStateIcon}}</div>\n <div class="carousel__empty-text">{{emptyStateText}}</div>\n </div>\n </ng-template>\n </div>\n </ng-template>\n </div>\n </div>\n\n <div *ngIf="showNavigation && hasItems()" [ngClass]="getNavControlsClass()" [ngStyle]="getNavControlsStyle()"> \n <button \n [class.carousel__nav-button--disabled]="!showPrevButton" \n [disabled]="!showPrevButton" \n [ngStyle]="getPrevButtonFullStyles()" \n (click)="previous()" \n style="pointer-events:auto;"\n class="carousel__nav-button">\n <span class="carousel__nav-icon" [ngStyle]="getIconStyles(false)">{{ prevIcon }}</span>\n </button>\n \n <button\n [class.carousel__nav-button--disabled]="!showNextButton"\n [disabled]="!showNextButton"\n [ngStyle]="getNextButtonFullStyles()"\n (click)="next()"\n style="pointer-events:auto;"\n class="carousel__nav-button">\n <span class="carousel__nav-icon" [ngStyle]="getIconStyles(true)">{{ nextIcon }}</span>\n </button>\n </div>\n\n \x3c!-- Carousel indicators --\x3e\n <div\n *ngIf="showIndicators && hasItems() && filteredItems.length > 1" \n class="carousel__indicators" \n [ngStyle]="getIndicatorContainerStyles()">\n <div\n *ngFor="let slide of filteredItems; let i = index"\n class="carousel__indicator"\n [class.carousel__indicator--active]="i === currentIndex"\n [ngStyle]="getIndicatorItemStyles(i)"\n (click)="goToSlide(i)">\n </div>\n </div>\n</div>\n',styles:[".carousel{--carousel-item-bg: #fff;--carousel-item-border-color: #e0e0e0;--carousel-empty-bg: #fff;--carousel-empty-color: #666;--carousel-nav-bg: #fff;--carousel-nav-border-color: #e0e0e0;--carousel-nav-disabled-bg: #f5f5f5;--carousel-nav-disabled-border: #ddd;--carousel-nav-disabled-opacity: .4;--carousel-empty-icon-size: 32px;--carousel-empty-text-size: 16px;--carousel-nav-icon-size: 16px;--carousel-nav-item-gap: 24px;--carousel-indicator-active-color: #333;--carousel-indicator-inactive-color: #ccc;--carousel-z-index: 100;position:relative;overflow:hidden;display:flex;flex-direction:column;border-radius:inherit;height:100%;min-height:inherit;box-sizing:border-box;width:fit-content}.carousel--vertical{flex-direction:column}.carousel__wrapper{flex:1;overflow:hidden;position:relative;width:100%;border-radius:inherit;height:100%;min-height:inherit;display:flex;flex-direction:column;box-sizing:border-box}.carousel__track{display:flex;flex-wrap:nowrap;transition:transform var(--animation-duration, .3s) var(--animation-timing, ease);width:fit-content;min-width:100%;height:fit-content!important;min-height:fit-content!important;border-radius:inherit;align-items:stretch;flex:1;box-sizing:border-box}.carousel__track--vertical{flex-direction:column;width:100%;height:fit-content!important;min-height:100%;align-items:stretch}.carousel__item{flex:0 0 auto;box-sizing:border-box;border-radius:inherit;min-height:100px;height:100%;display:flex;flex-direction:column;position:relative;overflow:hidden}.carousel__item[style*=--item-width]{flex:0 0 var(--item-width)!important;width:var(--item-width)!important;max-width:var(--item-width)!important;min-width:var(--item-width)!important}.carousel__item[style*=--item-height]{height:var(--item-height)!important;min-height:var(--item-height)!important;max-height:var(--item-height)!important}.carousel__item>*{height:100%!important;min-height:inherit!important;flex:1 1 auto!important;display:flex!important;flex-direction:column!important;box-sizing:border-box!important;overflow:hidden}.carousel--vertical .carousel__item{width:100%}.carousel__item-default{background:var(--carousel-item-bg);height:100%;width:100%;display:flex;align-items:center;justify-content:center;border:1px solid var(--carousel-item-border-color);border-radius:inherit;padding:20px;box-sizing:border-box;flex:1}.carousel__empty-container{width:100%;box-sizing:border-box;border-radius:inherit;flex:1;display:flex;align-items:center;justify-content:center;min-height:inherit;height:100%}.carousel__empty-state{background:var(--carousel-empty-bg);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:30px;text-align:center;color:var(--carousel-empty-color);border-radius:inherit;min-height:200px;width:100%;height:100%;flex:1}.carousel__nav-controls{position:absolute;display:flex;gap:0;z-index:var(--carousel-z-index);pointer-events:none;padding:0;margin:0;width:100%;height:100%;top:0;left:0}.carousel__nav-button{background:var(--carousel-nav-bg);border:1px solid var(--carousel-nav-border-color);width:32px;height:32px;padding:0;margin:0;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:var(--carousel-z-index);position:absolute;outline:none;box-shadow:0 2px 4px #0000001a}.carousel__nav-button:hover:not(.carousel__nav-button--disabled){opacity:.9;box-shadow:0 3px 6px #00000026}.carousel__nav-button--disabled{opacity:var(--carousel-nav-disabled-opacity);cursor:not-allowed;background-color:var(--carousel-nav-disabled-bg);border-color:var(--carousel-nav-disabled-border);pointer-events:none}.carousel__nav-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:var(--carousel-nav-icon-size);line-height:1}.carousel--vertical .carousel__nav-icon{transform:rotate(90deg)}.carousel__empty-icon{font-size:var(--carousel-empty-icon-size);margin-bottom:12px}.carousel__empty-text{font-size:var(--carousel-empty-text-size);margin-bottom:12px}.carousel__indicators{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:5px;position:absolute;bottom:4px;left:50%;transform:translate(-50%);z-index:100;pointer-events:auto}.carousel--vertical .carousel__indicators{flex-direction:row;bottom:10px;left:50%;transform:translate(-50%)}.carousel__indicator{transition:all .3s ease-in-out;background-color:var(--carousel-indicator-inactive-color, #ccc);opacity:.5;cursor:pointer;border-radius:50%;width:10px;height:10px;min-width:8px;min-height:8px}.carousel__indicator:hover{opacity:.8}.carousel__indicator--active{transform:scale(1.2);background-color:var(--carousel-indicator-active-color, #333);opacity:1;animation:carousel__indicator-pulse 1s infinite alternate}:root{--carousel-nav-bg: #fff;--carousel-nav-border-color: #e0e0e0;--carousel-nav-disabled-bg: #f5f5f5;--carousel-nav-disabled-border: #ddd;--carousel-nav-disabled-opacity: .4}@keyframes carousel__indicator-pulse{0%{transform:scale(1.2);box-shadow:0 0 #3333334d}to{transform:scale(1.3);box-shadow:0 0 0 5px #3330}}@keyframes carousel__indicator-expand{0%{width:8px}to{width:36px}}\n"],dependencies:[{kind:"ngmodule",type:a},{kind:"directive",type:f.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:f.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:f.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:f.NgTemplateOutlet,selector:"[ngTemplateOutlet]",inputs:["ngTemplateOutletContext","ngTemplateOutlet","ngTemplateOutletInjector"]},{kind:"directive",type:f.NgStyle,selector:"[ngStyle]",inputs:["ngStyle"]}]}),p.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"14.3.0",ngImport:p,type:CarouselComponent,decorators:[{type:t,args:[{selector:"carousel",standalone:!0,imports:[a],providers:[u],template:'<div class="carousel" \n [class.carousel--vertical]="isVertical" \n [ngClass]="configs.containerClass" \n [ngStyle]="containerStyles"\n [style.height]="configs.containerHeight || \'auto\'"\n [style.width]="configs.containerWidth || \'auto\'">\n <div\n #wrapper\n [style.--content-padding]="contentPadding"\n class="carousel__wrapper">\n <div\n #track\n [ngStyle]="trackStyles"\n [class.carousel__track--vertical]="isVertical"\n [style.--animation-duration]="animationDuration"\n [style.--animation-timing]="animationTiming"\n class="carousel__track">\n <ng-container *ngIf="hasItems(); else emptyState">\n <ng-container *ngFor="let slide of filteredItems; let i = index">\n <div class="carousel__item" \n [ngClass]="configs.itemClass" \n [ngStyle]="getItemStyle(i)" \n [style.--item-width]="configs.itemWidth || \'auto\'"\n [style.--item-height]="configs.itemHeight || \'auto\'">\n <ng-container *ngIf="itemTemplate; else defaultTemplate">\n <ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: slide, index: i }"></ng-container>\n </ng-container>\n <ng-template #defaultTemplate>\n <div class="carousel__item-default">\n {{ slide }}\n </div>\n </ng-template>\n </div>\n </ng-container>\n </ng-container>\n \n <ng-template #emptyState>\n <div class="carousel__empty-container" [ngStyle]="getEmptyStateContainerStyle()">\n <ng-container *ngIf="emptyStateTemplate; else defaultEmptyState">\n <ng-container *ngTemplateOutlet="emptyStateTemplate; context: { $implicit: emptyStateText }"></ng-container>\n </ng-container>\n <ng-template #defaultEmptyState>\n <div class="carousel__empty-state" [style.color]="emptyStateTextColor">\n <div *ngIf="showEmptyStateIcon" class="carousel__empty-icon">{{emptyStateIcon}}</div>\n <div class="carousel__empty-text">{{emptyStateText}}</div>\n </div>\n </ng-template>\n </div>\n </ng-template>\n </div>\n </div>\n\n <div *ngIf="showNavigation && hasItems()" [ngClass]="getNavControlsClass()" [ngStyle]="getNavControlsStyle()"> \n <button \n [class.carousel__nav-button--disabled]="!showPrevButton" \n [disabled]="!showPrevButton" \n [ngStyle]="getPrevButtonFullStyles()" \n (click)="previous()" \n style="pointer-events:auto;"\n class="carousel__nav-button">\n <span class="carousel__nav-icon" [ngStyle]="getIconStyles(false)">{{ prevIcon }}</span>\n </button>\n \n <button\n [class.carousel__nav-button--disabled]="!showNextButton"\n [disabled]="!showNextButton"\n [ngStyle]="getNextButtonFullStyles()"\n (click)="next()"\n style="pointer-events:auto;"\n class="carousel__nav-button">\n <span class="carousel__nav-icon" [ngStyle]="getIconStyles(true)">{{ nextIcon }}</span>\n </button>\n </div>\n\n \x3c!-- Carousel indicators --\x3e\n <div\n *ngIf="showIndicators && hasItems() && filteredItems.length > 1" \n class="carousel__indicators" \n [ngStyle]="getIndicatorContainerStyles()">\n <div\n *ngFor="let slide of filteredItems; let i = index"\n class="carousel__indicator"\n [class.carousel__indicator--active]="i === currentIndex"\n [ngStyle]="getIndicatorItemStyles(i)"\n (click)="goToSlide(i)">\n </div>\n </div>\n</div>\n',styles:[".carousel{--carousel-item-bg: #fff;--carousel-item-border-color: #e0e0e0;--carousel-empty-bg: #fff;--carousel-empty-color: #666;--carousel-nav-bg: #fff;--carousel-nav-border-color: #e0e0e0;--carousel-nav-disabled-bg: #f5f5f5;--carousel-nav-disabled-border: #ddd;--carousel-nav-disabled-opacity: .4;--carousel-empty-icon-size: 32px;--carousel-empty-text-size: 16px;--carousel-nav-icon-size: 16px;--carousel-nav-item-gap: 24px;--carousel-indicator-active-color: #333;--carousel-indicator-inactive-color: #ccc;--carousel-z-index: 100;position:relative;overflow:hidden;display:flex;flex-direction:column;border-radius:inherit;height:100%;min-height:inherit;box-sizing:border-box;width:fit-content}.carousel--vertical{flex-direction:column}.carousel__wrapper{flex:1;overflow:hidden;position:relative;width:100%;border-radius:inherit;height:100%;min-height:inherit;display:flex;flex-direction:column;box-sizing:border-box}.carousel__track{display:flex;flex-wrap:nowrap;transition:transform var(--animation-duration, .3s) var(--animation-timing, ease);width:fit-content;min-width:100%;height:fit-content!important;min-height:fit-content!important;border-radius:inherit;align-items:stretch;flex:1;box-sizing:border-box}.carousel__track--vertical{flex-direction:column;width:100%;height:fit-content!important;min-height:100%;align-items:stretch}.carousel__item{flex:0 0 auto;box-sizing:border-box;border-radius:inherit;min-height:100px;height:100%;display:flex;flex-direction:column;position:relative;overflow:hidden}.carousel__item[style*=--item-width]{flex:0 0 var(--item-width)!important;width:var(--item-width)!important;max-width:var(--item-width)!important;min-width:var(--item-width)!important}.carousel__item[style*=--item-height]{height:var(--item-height)!important;min-height:var(--item-height)!important;max-height:var(--item-height)!important}.carousel__item>*{height:100%!important;min-height:inherit!important;flex:1 1 auto!important;display:flex!important;flex-direction:column!important;box-sizing:border-box!important;overflow:hidden}.carousel--vertical .carousel__item{width:100%}.carousel__item-default{background:var(--carousel-item-bg);height:100%;width:100%;display:flex;align-items:center;justify-content:center;border:1px solid var(--carousel-item-border-color);border-radius:inherit;padding:20px;box-sizing:border-box;flex:1}.carousel__empty-container{width:100%;box-sizing:border-box;border-radius:inherit;flex:1;display:flex;align-items:center;justify-content:center;min-height:inherit;height:100%}.carousel__empty-state{background:var(--carousel-empty-bg);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:30px;text-align:center;color:var(--carousel-empty-color);border-radius:inherit;min-height:200px;width:100%;height:100%;flex:1}.carousel__nav-controls{position:absolute;display:flex;gap:0;z-index:var(--carousel-z-index);pointer-events:none;padding:0;margin:0;width:100%;height:100%;top:0;left:0}.carousel__nav-button{background:var(--carousel-nav-bg);border:1px solid var(--carousel-nav-border-color);width:32px;height:32px;padding:0;margin:0;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:var(--carousel-z-index);position:absolute;outline:none;box-shadow:0 2px 4px #0000001a}.carousel__nav-button:hover:not(.carousel__nav-button--disabled){opacity:.9;box-shadow:0 3px 6px #00000026}.carousel__nav-button--disabled{opacity:var(--carousel-nav-disabled-opacity);cursor:not-allowed;background-color:var(--carousel-nav-disabled-bg);border-color:var(--carousel-nav-disabled-border);pointer-events:none}.carousel__nav-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:var(--carousel-nav-icon-size);line-height:1}.carousel--vertical .carousel__nav-icon{transform:rotate(90deg)}.carousel__empty-icon{font-size:var(--carousel-empty-icon-size);margin-bottom:12px}.carousel__empty-text{font-size:var(--carousel-empty-text-size);margin-bottom:12px}.carousel__indicators{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:5px;position:absolute;bottom:4px;left:50%;transform:translate(-50%);z-index:100;pointer-events:auto}.carousel--vertical .carousel__indicators{flex-direction:row;bottom:10px;left:50%;transform:translate(-50%)}.carousel__indicator{transition:all .3s ease-in-out;background-color:var(--carousel-indicator-inactive-color, #ccc);opacity:.5;cursor:pointer;border-radius:50%;width:10px;height:10px;min-width:8px;min-height:8px}.carousel__indicator:hover{opacity:.8}.carousel__indicator--active{transform:scale(1.2);background-color:var(--carousel-indicator-active-color, #333);opacity:1;animation:carousel__indicator-pulse 1s infinite alternate}:root{--carousel-nav-bg: #fff;--carousel-nav-border-color: #e0e0e0;--carousel-nav-disabled-bg: #f5f5f5;--carousel-nav-disabled-border: #ddd;--carousel-nav-disabled-opacity: .4}@keyframes carousel__indicator-pulse{0%{transform:scale(1.2);box-shadow:0 0 #3333334d}to{transform:scale(1.3);box-shadow:0 0 0 5px #3330}}@keyframes carousel__indicator-expand{0%{width:8px}to{width:36px}}\n"]}]}],propDecorators:{slides:[{type:e}],configs:[{type:e}],itemTemplate:[{type:i,args:["carouselItem"]}],emptyStateTemplate:[{type:i,args:["emptyState"]}],trackElement:[{type:n,args:["track"]}],wrapperElement:[{type:n,args:["wrapper"]}]}});