UNPKG

@ipi-soft/ng-components

Version:

Custom Angular Components

162 lines (158 loc) 13.4 kB
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