/** * Copyright Aquera Inc 2025 * * This source code is licensed under the BSD-3-Clause license found in the * LICENSE file in the root directory of this source tree. */ import type NileCarouselItem from './nile-carousel-item/nile-carousel-item'; import { clamp } from '../internal/math'; import { prefersReducedMotion } from '../internal/animate'; export function isCarouselItem(element: HTMLElement): boolean { return element.tagName.toLowerCase() === 'nile-carousel-item'; } export function findMostVisibleSlide( slides: NileCarouselItem[], scrollContainer: HTMLElement ): NileCarouselItem | null { if (!slides.length || !scrollContainer) { return null; } const scrollContainerRect = scrollContainer.getBoundingClientRect(); let mostVisibleSlide: NileCarouselItem | null = null; let maxVisibleArea = 0; slides.forEach((slide) => { const slideRect = slide.getBoundingClientRect(); const visibleWidth = Math.max( 0, Math.min(slideRect.right, scrollContainerRect.right) - Math.max(slideRect.left, scrollContainerRect.left) ); if (visibleWidth > maxVisibleArea) { maxVisibleArea = visibleWidth; mostVisibleSlide = slide; } }); return mostVisibleSlide; } export function getPageCount(slidesCount: number, slidesPerPage: number, slidesPerMove: number): number { const pages = (slidesCount - slidesPerPage) / slidesPerMove + 1; return Math.ceil(pages); } export function getCurrentPage(activeSlide: number, slidesPerMove: number): number { return Math.floor(activeSlide / slidesPerMove); } export function canScrollNext(currentPage: number, pageCount: number, loop: boolean = false): boolean { if (loop) { return true; } return currentPage < pageCount - 1; } export function canScrollPrev(currentPage: number, loop: boolean = false): boolean { if (loop) { return true; } return currentPage > 0; } export function shouldSnapToSlide(slideIndex: number, slidesPerMove: number): boolean { return (slideIndex + slidesPerMove) % slidesPerMove === 0; } export function calculateScrollPosition( slideRect: DOMRect, containerRect: DOMRect, containerScrollLeft: number, containerScrollTop: number ): { left: number; top: number } { const nextLeft = slideRect.left - containerRect.left; const nextTop = slideRect.top - containerRect.top; return { left: nextLeft + containerScrollLeft, top: nextTop + containerScrollTop }; } export function scrollToSlide( slide: HTMLElement, scrollContainer: HTMLElement, behavior: ScrollBehavior = 'smooth', setPendingSlideChange: (value: boolean) => void ): void { setPendingSlideChange(true); window.requestAnimationFrame(() => { if (!scrollContainer) { return; } const scrollContainerRect = scrollContainer.getBoundingClientRect(); const nextSlideRect = slide.getBoundingClientRect(); const nextLeft = nextSlideRect.left - scrollContainerRect.left; const nextTop = nextSlideRect.top - scrollContainerRect.top; if (nextLeft || nextTop) { const scrollPosition = calculateScrollPosition( nextSlideRect, scrollContainerRect, scrollContainer.scrollLeft, scrollContainer.scrollTop ); setPendingSlideChange(true); scrollContainer.scrollTo({ left: scrollPosition.left, top: scrollPosition.top, behavior }); } else { setPendingSlideChange(false); } }); } export function goToSlide( index: number, slides: HTMLElement[], slidesPerPage: number, behavior: ScrollBehavior = 'smooth', loop: boolean = false ): { newActiveSlide: number; slideToScroll: HTMLElement } | null { if (!slides.length) { return null; } let newActiveSlide: number; let nextSlideIndex: number; if (loop) { const totalSlides = slides.length; nextSlideIndex = ((index % totalSlides) + totalSlides) % totalSlides; newActiveSlide = clamp(nextSlideIndex, 0, slides.length - slidesPerPage); } else { newActiveSlide = clamp(index, 0, slides.length - slidesPerPage); nextSlideIndex = clamp(index, 0, slides.length - 1); } const nextSlide = slides[nextSlideIndex]; return { newActiveSlide, slideToScroll: nextSlide }; }