UNPKG

@serene-dev/ng-carousel

Version:

The library was created to help with Carousel implementations.

207 lines (202 loc) 21.9 kB
import * as i0 from '@angular/core'; import { input, EventEmitter, signal, computed, Component, Input, Output } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; /** * Vanilla Carousel for Angular */ class NgCarouselComponent { constructor() { /** * Template to show if the items array is empty */ this.noItemsTemplate = input(); /** * It should be set when you select template as an item type. It will be used to render the custom content for the item's slide. * * A reference of the slide will be sent as a parameter into the template */ this.customItemTemplate = input(); /** * It will be embedded inside the buttons. * * A reference of the arrow navigation button will be sent as a parameter into the template */ this.customArrowContentTemplate = input(); /** * Number of items to show per view */ this.numberPerView = input(1); /** * Use the current index indicators */ this.useIndicators = input(true); /** * Use the arrow buttons */ this.useArrowButtons = input(true); /** * Automatically play carousel */ this.autoplay = true; /** * Speed of autoplay in milliseconds */ this.autoplaySpeed = 2000; /** * Automatically play videos */ this.autoplayVideo = input(true); /** * Make slide show restart at the end of the show */ this.continuous = input(true); /** * Custom class on the navigation buttons */ this.navBtnClass = input(); /** * Custom class for the item label */ this.labelClass = input(); /** * Pause slider on hover */ this.pauseOnHover = true; /** * Custom class for the item sublabel */ this.subLabelClass = input(); /** * Object fit for the items in the slide show */ this.contentFit = input('cover'); /** * Emitted when the current index changes. The current index and items are emitted. */ this.slideChanged = new EventEmitter(); /** * The current index. */ this.currentIndex = signal(0); /** * Group of items */ this.itemsGroup = signal([]); this.hasItemsGroup = computed(() => this.itemsGroup()?.length > 0); this.itemsGroupLength = computed(() => this.itemsGroup()?.length); this.disableNext = computed(() => { const currentIndex = this.currentIndex(), itemsGroupLength = this.itemsGroupLength(), continuous = this.continuous(); return currentIndex == itemsGroupLength && !continuous; }); this.onHover = false; } ngOnChanges(changes) { this.calculateGroup(); // debugger // if(changes.items.) // this.items?.forEach((item) => { // if (item.type == 'template' && !this.customItemTemplate) // throw ( // item.src + // ` needs a custom template because the item type is template` // ); // }); this.resetInterval(); } resetInterval() { if (this.intervaler) clearInterval(this.intervaler); if (this.autoplay) this.intervaler = setInterval(() => { if (!this.onHover) this.next(); }, this.autoplaySpeed); } ngOnDestroy() { if (this.intervaler) clearInterval(this.intervaler); } handleMouseOver() { if (this.pauseOnHover) this.onHover = true; } handleMouseOut() { if (this.pauseOnHover) this.onHover = false; } calculateGroup() { const numberPerView = this.numberPerView(); if (this.items) this.itemsGroup.set(new Array(Math.ceil((this.items.length || 0) / (numberPerView || 1))) .fill(null) .map((x, index) => { const end = index * (numberPerView || 1) + (numberPerView || 1); // debugger; return { group: this.items.slice(index * (numberPerView || 1), index * (numberPerView || 1) + (numberPerView || 1)), }; })); else this.itemsGroup.set([]); // debugger; } /** * Show the next slide */ next() { const currentIndex = this.currentIndex(); if (this.itemsGroupLength() > currentIndex + 1) this.currentIndex.set(currentIndex + 1); else if (this.continuous()) this.currentIndex.set(0); this.slideChanged.emit({ index: this.currentIndex(), currentItems: this.itemsGroup()[this.currentIndex()]?.group, }); } /** * Show the previous slide */ previous() { const currentIndex = this.currentIndex(); if (this.itemsGroupLength() > currentIndex + 1) this.currentIndex.set(currentIndex - 1); else if (this.continuous()) this.currentIndex.set(this.itemsGroupLength() - 1); this.slideChanged.emit({ index: this.currentIndex(), currentItems: this.itemsGroup()[this.currentIndex()]?.group, }); } /**Set the current index directly */ setCurrentIndex(index) { if (this.itemsGroup()?.length > index) this.currentIndex.set(index); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgCarouselComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.0", type: NgCarouselComponent, isStandalone: true, selector: "ng-carousel", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: false, isRequired: true, transformFunction: null }, noItemsTemplate: { classPropertyName: "noItemsTemplate", publicName: "noItemsTemplate", isSignal: true, isRequired: false, transformFunction: null }, customItemTemplate: { classPropertyName: "customItemTemplate", publicName: "customItemTemplate", isSignal: true, isRequired: false, transformFunction: null }, customArrowContentTemplate: { classPropertyName: "customArrowContentTemplate", publicName: "customArrowContentTemplate", isSignal: true, isRequired: false, transformFunction: null }, numberPerView: { classPropertyName: "numberPerView", publicName: "numberPerView", isSignal: true, isRequired: false, transformFunction: null }, useIndicators: { classPropertyName: "useIndicators", publicName: "useIndicators", isSignal: true, isRequired: false, transformFunction: null }, useArrowButtons: { classPropertyName: "useArrowButtons", publicName: "useArrowButtons", isSignal: true, isRequired: false, transformFunction: null }, autoplay: { classPropertyName: "autoplay", publicName: "autoplay", isSignal: false, isRequired: false, transformFunction: null }, autoplaySpeed: { classPropertyName: "autoplaySpeed", publicName: "autoplaySpeed", isSignal: false, isRequired: false, transformFunction: null }, autoplayVideo: { classPropertyName: "autoplayVideo", publicName: "autoplayVideo", isSignal: true, isRequired: false, transformFunction: null }, continuous: { classPropertyName: "continuous", publicName: "continuous", isSignal: true, isRequired: false, transformFunction: null }, navBtnClass: { classPropertyName: "navBtnClass", publicName: "navBtnClass", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, pauseOnHover: { classPropertyName: "pauseOnHover", publicName: "pauseOnHover", isSignal: false, isRequired: false, transformFunction: null }, subLabelClass: { classPropertyName: "subLabelClass", publicName: "subLabelClass", isSignal: true, isRequired: false, transformFunction: null }, contentFit: { classPropertyName: "contentFit", publicName: "contentFit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { slideChanged: "slideChanged" }, usesOnChanges: true, ngImport: i0, template: "@if (hasItemsGroup()) {\n <div class=\"ng-carousel\" (mouseover)=\"handleMouseOver()\" (mouseout)=\"handleMouseOut()\" #cont>\n @for (group of itemsGroup(); track group; let index = $index) {\n <div class=\"ng-carousel-item-group\" [hidden]=\"currentIndex() != index\">\n @for (item of group.group; track item) {\n <div class=\"ng-carousel-item\" [style.width.px]=\"cont.offsetWidth / numberPerView()\">\n @switch (item.type) {\n @case ('template') {\n <ng-container\n *ngTemplateOutlet=\"customItemTemplate()!; context: { $implicit: item }\"\n />\n }\n @case ('video') {\n <video\n src=\"{{ item.src }}\"\n [autoplay]=\"autoplayVideo()\"\n [controls]=\"false\"\n attr.aria-label=\"{{ item.label || 'slide ' }}\"\n class=\"{{ contentFit() }}\"\n ></video>\n }\n @default {\n <img\n src=\"{{ item.src }}\"\n attr.aria-label=\"{{ item.label || 'slide ' }}\"\n class=\"{{ contentFit() }}\"\n />\n }\n }\n @if (item.label) {\n <div class=\"ng-carousel-label-cont {{ labelClass() }}\">\n <div>\n <div class=\"ng-carousel-label\">{{ item.label }}</div>\n @if (item.subLabel) {\n <div class=\"ng-carousel-sub-label {{ subLabelClass() }} \">\n {{ item.subLabel }}\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n @if (useIndicators()) {\n <div class=\"indicators\">\n @for (item of itemsGroup(); track item; let index = $index) {\n <div\n (click)=\"setCurrentIndex(index)\"\n class=\"indicator-item\"\n [ngClass]=\"{ active: currentIndex() == index }\"\n ></div>\n }\n </div>\n }\n @if (useArrowButtons()) {\n <div class=\"nav-btn-cont left\">\n <button\n (click)=\"previous();resetInterval()\"\n #btnLeft\n [disabled]=\"currentIndex() == 0\"\n class=\"nav-btn left {{ navBtnClass() }}\"\n >\n @if (customArrowContentTemplate()) {\n <ng-container\n *ngTemplateOutlet=\"\n customArrowContentTemplate();\n context: { $implicit: { isBack: true, isForward: false } }\n \"\n />\n } @else {\n <\n }\n </button>\n </div>\n <div class=\"nav-btn-cont right\">\n <button\n (click)=\"next();resetInterval()\"\n #btnRight\n [disabled]=\"disableNext()\"\n class=\"nav-btn {{ navBtnClass() }}\"\n >\n @if (customArrowContentTemplate()) {\n <ng-container\n *ngTemplateOutlet=\"\n customArrowContentTemplate();\n context: { $implicit: { isBack: false, isForward: true } }\n \"\n />\n } @else {\n >\n }\n </button>\n </div>\n }\n </div>\n} @else {\n <ng-template [ngTemplateOutlet]=\"noItemsTemplate() || _noItemsTemplate\"></ng-template>\n}\n\n<ng-template #_noItemsTemplate> There are no items to show </ng-template>\n<!-- <div>{{ currentIndex }}</div> -->\n", styles: [".ng-carousel{--active: #f50;--transBG: #0000002b;--padding: 10px;--border-radius: 10px;--border-radius-small: 2px;position:relative;overflow:auto;width:100%;height:100%}.ng-carousel::-webkit-scrollbar{display:none}.ng-carousel{-ms-overflow-style:none;scrollbar-width:none}.ng-carousel .ng-carousel-item-group{width:100%;height:100%;display:flex}.ng-carousel .ng-carousel-item-group .ng-carousel-item{display:inline;padding:10px;position:relative;height:100%}.ng-carousel .ng-carousel-item-group .ng-carousel-item img,.ng-carousel .ng-carousel-item-group .ng-carousel-item video{width:100%;height:100%;border-radius:var(--border-radius);object-position:center}.ng-carousel .ng-carousel-item-group .ng-carousel-item img.contain,.ng-carousel .ng-carousel-item-group .ng-carousel-item video.contain{object-fit:contain}.ng-carousel .ng-carousel-item-group .ng-carousel-item img.cover,.ng-carousel .ng-carousel-item-group .ng-carousel-item video.cover{object-fit:cover}.ng-carousel .ng-carousel-item-group .ng-carousel-item .ng-carousel-label-cont{font-size:20px;text-align:center;position:absolute;bottom:calc(var(--padding) * 4);display:flex;justify-content:center;width:100%;right:0;left:0}.ng-carousel .ng-carousel-item-group .ng-carousel-item .ng-carousel-label-cont .ng-carousel-label,.ng-carousel .ng-carousel-item-group .ng-carousel-item .ng-carousel-label-cont .ng-carousel-sub-label{background-color:var(--transBG);padding:var(--padding);border-radius:var(--border-radius-small);color:var(--active);line-height:10px}.ng-carousel .ng-carousel-item-group .ng-carousel-item .ng-carousel-label-cont .ng-carousel-sub-label{font-size:15px;margin-top:var(--padding)}.ng-carousel .nav-btn-cont{display:flex;align-items:center;justify-content:center;position:absolute;top:0;bottom:0}.ng-carousel .nav-btn-cont.left{left:calc(var(--padding) + 10px)}.ng-carousel .nav-btn-cont.right{right:calc(var(--padding) + 10px)}.ng-carousel .nav-btn-cont .nav-btn{background-color:var(--transBG);border-radius:50%;border:none;padding:var(--padding);line-height:10px;color:var(--active)}.ng-carousel .nav-btn-cont .nav-btn[disabled]{cursor:not-allowed}.ng-carousel .indicators{display:flex;align-items:center;justify-content:center;position:absolute;width:100%;bottom:calc(var(--padding) + 10px)}.ng-carousel .indicators .indicator-item{width:10px;height:5px;background-color:var(--transBG);border-radius:var(--border-radius-small);margin:0 3px}.ng-carousel .indicators .indicator-item.active{background-color:var(--active)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgCarouselComponent, decorators: [{ type: Component, args: [{ selector: 'ng-carousel', imports: [CommonModule], template: "@if (hasItemsGroup()) {\n <div class=\"ng-carousel\" (mouseover)=\"handleMouseOver()\" (mouseout)=\"handleMouseOut()\" #cont>\n @for (group of itemsGroup(); track group; let index = $index) {\n <div class=\"ng-carousel-item-group\" [hidden]=\"currentIndex() != index\">\n @for (item of group.group; track item) {\n <div class=\"ng-carousel-item\" [style.width.px]=\"cont.offsetWidth / numberPerView()\">\n @switch (item.type) {\n @case ('template') {\n <ng-container\n *ngTemplateOutlet=\"customItemTemplate()!; context: { $implicit: item }\"\n />\n }\n @case ('video') {\n <video\n src=\"{{ item.src }}\"\n [autoplay]=\"autoplayVideo()\"\n [controls]=\"false\"\n attr.aria-label=\"{{ item.label || 'slide ' }}\"\n class=\"{{ contentFit() }}\"\n ></video>\n }\n @default {\n <img\n src=\"{{ item.src }}\"\n attr.aria-label=\"{{ item.label || 'slide ' }}\"\n class=\"{{ contentFit() }}\"\n />\n }\n }\n @if (item.label) {\n <div class=\"ng-carousel-label-cont {{ labelClass() }}\">\n <div>\n <div class=\"ng-carousel-label\">{{ item.label }}</div>\n @if (item.subLabel) {\n <div class=\"ng-carousel-sub-label {{ subLabelClass() }} \">\n {{ item.subLabel }}\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n @if (useIndicators()) {\n <div class=\"indicators\">\n @for (item of itemsGroup(); track item; let index = $index) {\n <div\n (click)=\"setCurrentIndex(index)\"\n class=\"indicator-item\"\n [ngClass]=\"{ active: currentIndex() == index }\"\n ></div>\n }\n </div>\n }\n @if (useArrowButtons()) {\n <div class=\"nav-btn-cont left\">\n <button\n (click)=\"previous();resetInterval()\"\n #btnLeft\n [disabled]=\"currentIndex() == 0\"\n class=\"nav-btn left {{ navBtnClass() }}\"\n >\n @if (customArrowContentTemplate()) {\n <ng-container\n *ngTemplateOutlet=\"\n customArrowContentTemplate();\n context: { $implicit: { isBack: true, isForward: false } }\n \"\n />\n } @else {\n <\n }\n </button>\n </div>\n <div class=\"nav-btn-cont right\">\n <button\n (click)=\"next();resetInterval()\"\n #btnRight\n [disabled]=\"disableNext()\"\n class=\"nav-btn {{ navBtnClass() }}\"\n >\n @if (customArrowContentTemplate()) {\n <ng-container\n *ngTemplateOutlet=\"\n customArrowContentTemplate();\n context: { $implicit: { isBack: false, isForward: true } }\n \"\n />\n } @else {\n >\n }\n </button>\n </div>\n }\n </div>\n} @else {\n <ng-template [ngTemplateOutlet]=\"noItemsTemplate() || _noItemsTemplate\"></ng-template>\n}\n\n<ng-template #_noItemsTemplate> There are no items to show </ng-template>\n<!-- <div>{{ currentIndex }}</div> -->\n", styles: [".ng-carousel{--active: #f50;--transBG: #0000002b;--padding: 10px;--border-radius: 10px;--border-radius-small: 2px;position:relative;overflow:auto;width:100%;height:100%}.ng-carousel::-webkit-scrollbar{display:none}.ng-carousel{-ms-overflow-style:none;scrollbar-width:none}.ng-carousel .ng-carousel-item-group{width:100%;height:100%;display:flex}.ng-carousel .ng-carousel-item-group .ng-carousel-item{display:inline;padding:10px;position:relative;height:100%}.ng-carousel .ng-carousel-item-group .ng-carousel-item img,.ng-carousel .ng-carousel-item-group .ng-carousel-item video{width:100%;height:100%;border-radius:var(--border-radius);object-position:center}.ng-carousel .ng-carousel-item-group .ng-carousel-item img.contain,.ng-carousel .ng-carousel-item-group .ng-carousel-item video.contain{object-fit:contain}.ng-carousel .ng-carousel-item-group .ng-carousel-item img.cover,.ng-carousel .ng-carousel-item-group .ng-carousel-item video.cover{object-fit:cover}.ng-carousel .ng-carousel-item-group .ng-carousel-item .ng-carousel-label-cont{font-size:20px;text-align:center;position:absolute;bottom:calc(var(--padding) * 4);display:flex;justify-content:center;width:100%;right:0;left:0}.ng-carousel .ng-carousel-item-group .ng-carousel-item .ng-carousel-label-cont .ng-carousel-label,.ng-carousel .ng-carousel-item-group .ng-carousel-item .ng-carousel-label-cont .ng-carousel-sub-label{background-color:var(--transBG);padding:var(--padding);border-radius:var(--border-radius-small);color:var(--active);line-height:10px}.ng-carousel .ng-carousel-item-group .ng-carousel-item .ng-carousel-label-cont .ng-carousel-sub-label{font-size:15px;margin-top:var(--padding)}.ng-carousel .nav-btn-cont{display:flex;align-items:center;justify-content:center;position:absolute;top:0;bottom:0}.ng-carousel .nav-btn-cont.left{left:calc(var(--padding) + 10px)}.ng-carousel .nav-btn-cont.right{right:calc(var(--padding) + 10px)}.ng-carousel .nav-btn-cont .nav-btn{background-color:var(--transBG);border-radius:50%;border:none;padding:var(--padding);line-height:10px;color:var(--active)}.ng-carousel .nav-btn-cont .nav-btn[disabled]{cursor:not-allowed}.ng-carousel .indicators{display:flex;align-items:center;justify-content:center;position:absolute;width:100%;bottom:calc(var(--padding) + 10px)}.ng-carousel .indicators .indicator-item{width:10px;height:5px;background-color:var(--transBG);border-radius:var(--border-radius-small);margin:0 3px}.ng-carousel .indicators .indicator-item.active{background-color:var(--active)}\n"] }] }], propDecorators: { items: [{ type: Input, args: [{ required: true }] }], autoplay: [{ type: Input }], autoplaySpeed: [{ type: Input }], pauseOnHover: [{ type: Input }], slideChanged: [{ type: Output }] } }); /* * Public API Surface of ng-carousel */ /** * Generated bundle index. Do not edit. */ export { NgCarouselComponent }; //# sourceMappingURL=serene-dev-ng-carousel.mjs.map