@ipi-soft/ng-components
Version:
Custom Angular Components
162 lines (158 loc) • 13.4 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, PLATFORM_ID, Component, ViewChild, HostListener } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { Subject, throttleTime } from 'rxjs';
class IpiCarouselComponent {
constructor() {
this.scrollProgress = 0;
this.scrollAnimationDuration = 500; // ms
this.scrollSubject = new Subject();
this.currentCenteredElemIndex = 0;
this.isProgrammaticScroll = false;
this.platformId = inject(PLATFORM_ID);
}
ngAfterViewInit() {
this.applyScrollSnapAlign();
if (isPlatformBrowser(this.platformId)) {
this.adjustCarouselSpacing();
}
this.scrollSubject.pipe(throttleTime(this.scrollAnimationDuration)).subscribe((direction) => {
this.scroll(direction);
});
this.updateCurrentCenterdElemIndex(this.carousel.nativeElement);
}
ngOnDestroy() {
if (this.scrollSubscription) {
this.scrollSubscription.unsubscribe();
}
}
scroll(direction) {
const carouselEl = this.carousel.nativeElement;
const children = Array.from(carouselEl.children);
if (direction === 'next') {
this.currentCenteredElemIndex = Math.min(this.currentCenteredElemIndex + 1, children.length - 1);
}
else {
this.currentCenteredElemIndex = Math.max(this.currentCenteredElemIndex - 1, 0);
}
const targetElement = children[this.currentCenteredElemIndex];
this.isProgrammaticScroll = true;
if (!targetElement) {
return;
}
switch (this.currentCenteredElemIndex) {
case 0:
carouselEl.scrollTo({ left: 0, behavior: 'smooth' });
break;
case children.length - 1:
const scrollEnd = carouselEl.scrollWidth - carouselEl.clientWidth;
carouselEl.scrollTo({ left: scrollEnd, behavior: 'smooth' });
break;
default:
targetElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
}
setTimeout(() => {
this.isProgrammaticScroll = false;
}, this.scrollAnimationDuration);
}
onResize() {
const carouselEl = this.carousel.nativeElement;
const carouselCenter = carouselEl.offsetWidth / 2;
const scrollLeft = carouselEl.scrollLeft;
const children = Array.from(carouselEl.children);
const currentlyCenteredElement = children.find(child => {
const childLeft = child.offsetLeft - scrollLeft;
const childCenter = childLeft + child.offsetWidth / 2;
return Math.abs(childCenter - carouselCenter) < child.offsetWidth / 2;
});
if (currentlyCenteredElement) {
const targetLeft = currentlyCenteredElement.offsetLeft;
const targetCenter = targetLeft + currentlyCenteredElement.offsetWidth / 2;
const newScrollPosition = targetCenter - carouselCenter;
carouselEl.scrollTo({ left: newScrollPosition, behavior: 'smooth' });
}
}
onNavigationKeydown(event, direction) {
if (event.code === 'Space' || event.code === 'Enter') {
this.scroll(direction);
return;
}
if (event.code === 'ArrowLeft' || event.code === 'ArrowRight') {
if (this.nextButton.nativeElement === document.activeElement) {
this.prevButton.nativeElement.focus();
}
else {
this.nextButton.nativeElement.focus();
}
}
}
onScroll(event) {
const container = event.target;
this.updateProgressBar(container);
if (this.isProgrammaticScroll) {
return;
}
this.updateCurrentCenterdElemIndex(container);
}
updateProgressBar(container) {
const scrollLeft = container.scrollLeft;
const scrollWidth = container.scrollWidth - container.clientWidth;
this.scrollProgress = ((scrollLeft / scrollWidth) * 100) - (104 / container.clientWidth);
}
updateCurrentCenterdElemIndex(container) {
const carouselEl = this.carousel.nativeElement;
const carouselWidth = carouselEl.offsetWidth;
const carouselCenter = container.scrollLeft + carouselWidth / 2;
const children = Array.from(carouselEl.children);
let closestIndex = 0;
let minDistance = Infinity;
for (let i = 0; i < children.length; i++) {
const childCenter = children[i].offsetLeft + children[i].offsetWidth / 2;
const distance = Math.abs(carouselCenter - childCenter);
if (distance < minDistance) {
minDistance = distance;
closestIndex = i;
}
}
this.currentCenteredElemIndex = closestIndex;
}
applyScrollSnapAlign() {
const children = this.carousel.nativeElement.children;
for (let i = 0; i < children.length; i++) {
children[i].style.scrollSnapAlign = 'center';
}
}
adjustCarouselSpacing() {
const carouselEl = this.carousel.nativeElement;
const carouselRect = carouselEl.getBoundingClientRect();
const rightCalculatedValue = window.innerWidth - carouselRect.right;
const leftCalculatedValue = carouselRect.left;
carouselEl.style.marginRight = `-${rightCalculatedValue}px`;
carouselEl.style.paddingRight = `${rightCalculatedValue}px`;
carouselEl.style.marginLeft = `-${leftCalculatedValue}px`;
carouselEl.style.paddingLeft = `${leftCalculatedValue}px`;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: IpiCarouselComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.4", type: IpiCarouselComponent, isStandalone: true, selector: "ipi-carousel", host: { listeners: { "window:resize": "onResize()" } }, viewQueries: [{ propertyName: "carousel", first: true, predicate: ["carousel"], descendants: true }, { propertyName: "prevButton", first: true, predicate: ["prevButton"], descendants: true }, { propertyName: "nextButton", first: true, predicate: ["nextButton"], descendants: true }], ngImport: i0, template: "<div class=\"carousel-container\">\n <div #carousel class=\"carousel\" (scroll)=\"onScroll($event)\">\n <ng-content></ng-content>\n </div>\n</div>\n\n<div class=\"carousel-navigation\">\n <div class=\"navigation-buttons-wrapper\">\n <div \n #prevButton\n tabindex=\"0\"\n class=\"chevron-wrapper\"\n aria-label=\"Scroll to previous item\"\n (click)=\"scrollSubject.next('prev')\"\n (keydown)=\"onNavigationKeydown($event, 'prev')\">\n <div class=\"chevron prev\"></div>\n </div>\n\n <div\n #nextButton\n tabindex=\"0\"\n class=\"chevron-wrapper\"\n aria-label=\"Scroll to next item\"\n (click)=\"scrollSubject.next('next')\"\n (keydown)=\"onNavigationKeydown($event, 'next')\">\n <div class=\"chevron next\"></div>\n </div>\n </div>\n\n <div #progressBarContainer class=\"scroll-bar-container\">\n <div class=\"scroll-indicator\" [style.width.%]=\"scrollProgress\"></div>\n </div>\n</div>\n", styles: [".carousel-container{width:100%;display:flex;align-items:center;background-color:var(--ipi-carousel-background-color, transparent);position:relative;margin-bottom:24px}.carousel{display:flex;flex-grow:1;overflow:scroll;flex-wrap:nowrap;scroll-behavior:smooth;scroll-snap-type:x mandatory;gap:16px}.carousel::-webkit-scrollbar{display:none}.carousel-navigation{display:flex;align-items:center;justify-content:flex-start;gap:24px}.navigation-buttons-wrapper{width:104px;height:48px;display:flex;gap:8px}.chevron-wrapper{min-width:40px;height:40px;display:flex;justify-content:center;align-items:center;position:relative;cursor:pointer;border:1px solid transparent;border-radius:50%}.chevron-wrapper:before{pointer-events:none;content:\"\";position:absolute;inset:0;border-radius:inherit;background:var(--ipi-carousel-button-border-color, #FFFFFF);padding:1px;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude}.chevron-wrapper:hover:before{background:var(--ipi-carousel-button-border-hover-color, #FFFFFF)}.chevron-wrapper:hover .chevron{border-top:3px solid var(--ipi-carousel-chevron-hover-color, #FFFFFF);border-left:3px solid var(--ipi-carousel-chevro-hover-color, #FFFFFF)}.chevron{width:8px;height:8px;flex-shrink:0;position:relative;transform-origin:center;transform:rotate(-135deg);border-top:3px solid var(--ipi-carousel-chevron-color, #FFFFFF);border-left:3px solid var(--ipi-carousel-chevron-color, #FFFFFF);border-top-left-radius:3px;border-top-right-radius:1px;border-bottom-left-radius:1px}.chevron.next{transform:rotate(135deg);left:-1px}.chevron.prev{transform:rotate(-45deg);right:0}.scroll-bar-container{width:100%;height:1px;overflow:hidden;position:relative;background-color:var(--ipi-carousel-scrollbar-container-color, #020617);border-radius:10px;margin:.5rem 0}.scroll-indicator{left:0;height:1px;background-color:var(--ipi-carousel-scrollbar-color, #FFFFFF);transition:width .1s}\n"] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: IpiCarouselComponent, decorators: [{
type: Component,
args: [{ selector: 'ipi-carousel', template: "<div class=\"carousel-container\">\n <div #carousel class=\"carousel\" (scroll)=\"onScroll($event)\">\n <ng-content></ng-content>\n </div>\n</div>\n\n<div class=\"carousel-navigation\">\n <div class=\"navigation-buttons-wrapper\">\n <div \n #prevButton\n tabindex=\"0\"\n class=\"chevron-wrapper\"\n aria-label=\"Scroll to previous item\"\n (click)=\"scrollSubject.next('prev')\"\n (keydown)=\"onNavigationKeydown($event, 'prev')\">\n <div class=\"chevron prev\"></div>\n </div>\n\n <div\n #nextButton\n tabindex=\"0\"\n class=\"chevron-wrapper\"\n aria-label=\"Scroll to next item\"\n (click)=\"scrollSubject.next('next')\"\n (keydown)=\"onNavigationKeydown($event, 'next')\">\n <div class=\"chevron next\"></div>\n </div>\n </div>\n\n <div #progressBarContainer class=\"scroll-bar-container\">\n <div class=\"scroll-indicator\" [style.width.%]=\"scrollProgress\"></div>\n </div>\n</div>\n", styles: [".carousel-container{width:100%;display:flex;align-items:center;background-color:var(--ipi-carousel-background-color, transparent);position:relative;margin-bottom:24px}.carousel{display:flex;flex-grow:1;overflow:scroll;flex-wrap:nowrap;scroll-behavior:smooth;scroll-snap-type:x mandatory;gap:16px}.carousel::-webkit-scrollbar{display:none}.carousel-navigation{display:flex;align-items:center;justify-content:flex-start;gap:24px}.navigation-buttons-wrapper{width:104px;height:48px;display:flex;gap:8px}.chevron-wrapper{min-width:40px;height:40px;display:flex;justify-content:center;align-items:center;position:relative;cursor:pointer;border:1px solid transparent;border-radius:50%}.chevron-wrapper:before{pointer-events:none;content:\"\";position:absolute;inset:0;border-radius:inherit;background:var(--ipi-carousel-button-border-color, #FFFFFF);padding:1px;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude}.chevron-wrapper:hover:before{background:var(--ipi-carousel-button-border-hover-color, #FFFFFF)}.chevron-wrapper:hover .chevron{border-top:3px solid var(--ipi-carousel-chevron-hover-color, #FFFFFF);border-left:3px solid var(--ipi-carousel-chevro-hover-color, #FFFFFF)}.chevron{width:8px;height:8px;flex-shrink:0;position:relative;transform-origin:center;transform:rotate(-135deg);border-top:3px solid var(--ipi-carousel-chevron-color, #FFFFFF);border-left:3px solid var(--ipi-carousel-chevron-color, #FFFFFF);border-top-left-radius:3px;border-top-right-radius:1px;border-bottom-left-radius:1px}.chevron.next{transform:rotate(135deg);left:-1px}.chevron.prev{transform:rotate(-45deg);right:0}.scroll-bar-container{width:100%;height:1px;overflow:hidden;position:relative;background-color:var(--ipi-carousel-scrollbar-container-color, #020617);border-radius:10px;margin:.5rem 0}.scroll-indicator{left:0;height:1px;background-color:var(--ipi-carousel-scrollbar-color, #FFFFFF);transition:width .1s}\n"] }]
}], ctorParameters: () => [], propDecorators: { carousel: [{
type: ViewChild,
args: ['carousel']
}], prevButton: [{
type: ViewChild,
args: ['prevButton']
}], nextButton: [{
type: ViewChild,
args: ['nextButton']
}], onResize: [{
type: HostListener,
args: ['window:resize']
}] } });
/**
* Generated bundle index. Do not edit.
*/
export { IpiCarouselComponent };
//# sourceMappingURL=ipi-soft-ng-components-carousel.mjs.map