import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, contentChildren, ElementRef, HostBinding, Inject, Input, Optional, ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { chevronBackOutline, chevronForwardOutline } from 'ionicons/icons'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, imports: [IonIcon, MatButtonModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-carousel', styleUrls: ['./carousel.component.scss'], templateUrl: './carousel.component.html' }) export class GfCarouselComponent { @HostBinding('class.animations-disabled') public readonly animationsDisabled: boolean; @Input('aria-label') public ariaLabel: string | undefined; @ViewChild('list') public list!: ElementRef; public items = contentChildren('carouselItem', { read: ElementRef }); public showPrevArrow = false; public showNextArrow = true; private index = 0; private position = 0; public constructor( @Optional() @Inject(ANIMATION_MODULE_TYPE) animationsModule?: string ) { this.animationsDisabled = animationsModule === 'NoopAnimations'; addIcons({ chevronBackOutline, chevronForwardOutline }); } public next() { for (let i = this.index; i < this.items().length; i++) { if (this.isOutOfView(i)) { this.index = i; this.scrollToActiveItem(); break; } } } public previous() { for (let i = this.index; i > -1; i--) { if (this.isOutOfView(i)) { this.index = i; this.scrollToActiveItem(); break; } } } private isOutOfView(index: number, side?: 'start' | 'end') { const { offsetWidth, offsetLeft } = this.items()[index].nativeElement; if ((!side || side === 'start') && offsetLeft - this.position < 0) { return true; } return ( (!side || side === 'end') && offsetWidth + offsetLeft - this.position > this.list.nativeElement.clientWidth ); } private scrollToActiveItem() { if (!this.isOutOfView(this.index)) { return; } let targetItemIndex = this.index; if (this.index > 0 && !this.isOutOfView(this.index - 1)) { targetItemIndex = this.items().findIndex((_, i) => !this.isOutOfView(i)) + 1; } this.position = this.items()[targetItemIndex].nativeElement.offsetLeft; this.list.nativeElement.style.transform = `translateX(-${this.position}px)`; this.showPrevArrow = this.index > 0; this.showNextArrow = false; for (let i = this.items().length - 1; i > -1; i--) { if (this.isOutOfView(i, 'end')) { this.showNextArrow = true; break; } } } }