feat(carousel): enhance carousel component with gap support and layout adjustments
This commit is contained in:
@@ -31,19 +31,23 @@ export type CarouselTrackBy<T> = (index: number, item: T) => unknown;
|
||||
export class CarouselComponent<T> implements AfterViewInit {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);
|
||||
private readonly viewport = viewChild.required<ElementRef<HTMLElement>>('viewport');
|
||||
|
||||
readonly items = input.required<readonly T[]>();
|
||||
readonly itemTemplate = input.required<TemplateRef<CarouselItemContext<T>>>();
|
||||
readonly trackBy = input<CarouselTrackBy<T>>((index) => index);
|
||||
readonly scrollStep = input<number | null>(null);
|
||||
readonly gap = input(0);
|
||||
readonly ariaLabel = input('Carrusel');
|
||||
readonly previousLabel = input('Mostrar elementos anteriores');
|
||||
readonly nextLabel = input('Mostrar elementos siguientes');
|
||||
|
||||
protected readonly canScrollPrevious = signal(false);
|
||||
protected readonly canScrollNext = signal(false);
|
||||
protected readonly viewportWidth = signal<number | null>(null);
|
||||
protected readonly hasMultipleItems = computed(() => this.items().length > 1);
|
||||
protected readonly resolvedGap = computed(() => Math.max(this.gap(), 0));
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (!isPlatformBrowser(this.platformId)) {
|
||||
@@ -51,18 +55,18 @@ export class CarouselComponent<T> implements AfterViewInit {
|
||||
}
|
||||
|
||||
const viewport = this.viewport().nativeElement;
|
||||
this.refreshNavigation();
|
||||
this.refreshLayout();
|
||||
|
||||
const resizeObserver =
|
||||
typeof ResizeObserver === 'undefined'
|
||||
? null
|
||||
: new ResizeObserver(() => this.refreshNavigation());
|
||||
resizeObserver?.observe(viewport);
|
||||
: new ResizeObserver(() => this.refreshLayout());
|
||||
resizeObserver?.observe(this.host.nativeElement);
|
||||
|
||||
const mutationObserver =
|
||||
typeof MutationObserver === 'undefined'
|
||||
? null
|
||||
: new MutationObserver(() => this.refreshNavigation());
|
||||
: new MutationObserver(() => this.refreshLayout());
|
||||
mutationObserver?.observe(viewport, { childList: true });
|
||||
|
||||
this.destroyRef.onDestroy(() => {
|
||||
@@ -109,7 +113,9 @@ export class CarouselComponent<T> implements AfterViewInit {
|
||||
const viewport = this.viewport().nativeElement;
|
||||
const configuredStep = this.scrollStep();
|
||||
const step =
|
||||
configuredStep !== null && configuredStep > 0 ? configuredStep : viewport.clientWidth;
|
||||
configuredStep !== null && configuredStep > 0
|
||||
? configuredStep
|
||||
: viewport.clientWidth + this.resolvedGap();
|
||||
|
||||
viewport.scrollBy({
|
||||
left: direction * step,
|
||||
@@ -125,4 +131,35 @@ export class CarouselComponent<T> implements AfterViewInit {
|
||||
this.canScrollPrevious.set(viewport.scrollLeft > tolerance);
|
||||
this.canScrollNext.set(viewport.scrollLeft < maxScrollLeft - tolerance);
|
||||
}
|
||||
|
||||
private refreshLayout(): void {
|
||||
const viewport = this.viewport().nativeElement;
|
||||
const firstItem = viewport.firstElementChild as HTMLElement | null;
|
||||
const availableWidth = this.host.nativeElement.clientWidth;
|
||||
|
||||
if (!firstItem || availableWidth <= 0) {
|
||||
this.viewportWidth.set(null);
|
||||
this.refreshNavigation();
|
||||
return;
|
||||
}
|
||||
|
||||
const itemWidth = firstItem.getBoundingClientRect().width;
|
||||
|
||||
if (itemWidth <= 0) {
|
||||
this.viewportWidth.set(null);
|
||||
this.refreshNavigation();
|
||||
return;
|
||||
}
|
||||
|
||||
const gap = this.resolvedGap();
|
||||
const fittingItems = Math.max(
|
||||
1,
|
||||
Math.floor((availableWidth + gap) / (itemWidth + gap)),
|
||||
);
|
||||
const visibleItems = Math.min(fittingItems, viewport.children.length);
|
||||
const fittedWidth = visibleItems * itemWidth + Math.max(visibleItems - 1, 0) * gap;
|
||||
|
||||
this.viewportWidth.set(Math.min(Math.ceil(fittedWidth), availableWidth));
|
||||
this.refreshNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user