UNPKG

16.5 kBSource Map (JSON)View Raw
1{"version":3,"file":"carousel.component.js","sourceRoot":"","sources":["../../src/carousel/carousel.component.ts"],"names":[],"mappings":"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","sourcesContent":["// todo: add animation\n\n/***\n * pause (not yet supported) (?string='hover') - event group name which pauses the cycling of the carousel, if hover pauses on mouseenter and resumes on mouseleave\n keyboard (not yet supported) (?boolean=true) - if false carousel will not react to keyboard events\n note: swiping not yet supported\n */\n/****\n * Problems:\n * 1) if we set an active slide via model changes, .active class remains on a current slide.\n * 2) if we have only one slide, we shouldn't show prev/next nav buttons\n * 3) if first or last slide is active and noWrap is true, there should be \"disabled\" class on the nav buttons.\n * 4) default interval should be equal 5000\n */\n\nimport { Component, Input, OnDestroy, Output, EventEmitter } from '@angular/core';\n\nimport { isBs3, LinkedList } from '../utils';\nimport { SlideComponent } from './slide.component';\nimport { CarouselConfig } from './carousel.config';\n\nexport enum Direction {UNKNOWN, NEXT, PREV}\n\n/**\n * Base element to create carousel\n */\n\nexport class CarouselComponent implements OnDestroy {\n /** If `true` — carousel will not cycle continuously and will have hard stops (prevent looping) */\n public noWrap: boolean;\n /** If `true` — will disable pausing on carousel mouse hover */\n public noPause: boolean;\n\n protected _currentActiveSlide: number;\n\n /** Will be emitted when active slide has been changed. Part of two-way-bindable [(activeSlide)] property */\n public activeSlideChange: EventEmitter <any> = new EventEmitter<any>(false);\n\n /** Index of currently displayed slide(started for 0) */\n \n public set activeSlide(index: number) {\n if (this._slides.length && index !== this._currentActiveSlide) {\n this._select(index);\n }\n }\n public get activeSlide(): number {\n return this._currentActiveSlide;\n }\n\n protected _interval: number;\n\n /**\n * Delay of item cycling in milliseconds. If false, carousel won't cycle automatically.\n */\n \n public get interval(): number {\n return this._interval;\n }\n public set interval(value: number) {\n this._interval = value;\n this.restartTimer();\n }\n\n protected _slides: LinkedList<SlideComponent> = new LinkedList<SlideComponent>();\n public get slides(): SlideComponent[] {\n return this._slides.toArray();\n }\n\n protected currentInterval: any;\n protected isPlaying: boolean;\n protected destroyed: boolean = false;\n\n public get isBs4():boolean {\n return !isBs3();\n }\n\n public constructor(config: CarouselConfig) {\n Object.assign(this, config);\n }\n\n public ngOnDestroy(): void {\n this.destroyed = true;\n }\n\n /**\n * Adds new slide. If this slide is first in collection - set it as active and starts auto changing\n * @param slide\n */\n public addSlide(slide: SlideComponent): void {\n this._slides.add(slide);\n if (this._slides.length === 1) {\n this._currentActiveSlide = void 0;\n this.activeSlide = 0;\n this.play();\n }\n }\n\n /**\n * Removes specified slide. If this slide is active - will roll to another slide\n * @param slide\n */\n public removeSlide(slide: SlideComponent): void {\n const remIndex = this._slides.indexOf(slide);\n\n if (this._currentActiveSlide === remIndex) {\n\n // removing of active slide\n let nextSlideIndex: number = void 0;\n if (this._slides.length > 1) {\n // if this slide last - will roll to first slide, if noWrap flag is FALSE or to previous, if noWrap is TRUE\n // in case, if this slide in middle of collection, index of next slide is same to removed\n nextSlideIndex = !this.isLast(remIndex) ? remIndex :\n this.noWrap ? remIndex - 1 : 0;\n }\n this._slides.remove(remIndex);\n\n // prevents exception with changing some value after checking\n setTimeout(() => {\n this._select(nextSlideIndex);\n }, 0);\n } else {\n this._slides.remove(remIndex);\n const currentSlideIndex = this.getCurrentSlideIndex();\n setTimeout(() => {\n // after removing, need to actualize index of current active slide\n this._currentActiveSlide = currentSlideIndex;\n this.activeSlideChange.emit(this._currentActiveSlide);\n }, 0);\n\n }\n }\n\n /**\n * Rolling to next slide\n * @param force: {boolean} if true - will ignore noWrap flag\n */\n public nextSlide(force: boolean = false): void {\n this.activeSlide = this.findNextSlideIndex(Direction.NEXT, force);\n }\n\n /**\n * Rolling to previous slide\n * @param force: {boolean} if true - will ignore noWrap flag\n */\n public previousSlide(force: boolean = false): void {\n this.activeSlide = this.findNextSlideIndex(Direction.PREV, force);\n }\n\n /**\n * Rolling to specified slide\n * @param index: {number} index of slide, which must be shown\n */\n public selectSlide(index: number): void {\n this.activeSlide = index;\n }\n\n /**\n * Starts a auto changing of slides\n */\n public play(): void {\n if (!this.isPlaying) {\n this.isPlaying = true;\n this.restartTimer();\n }\n }\n\n /**\n * Stops a auto changing of slides\n */\n public pause(): void {\n if (!this.noPause) {\n this.isPlaying = false;\n this.resetTimer();\n }\n }\n\n /**\n * Finds and returns index of currently displayed slide\n * @returns {number}\n */\n public getCurrentSlideIndex(): number {\n return this._slides.findIndex((slide: SlideComponent) => slide.active);\n }\n\n /**\n * Defines, whether the specified index is last in collection\n * @param index\n * @returns {boolean}\n */\n public isLast(index: number): boolean {\n return index + 1 >= this._slides.length;\n }\n\n /**\n * Defines next slide index, depending of direction\n * @param direction: Direction(UNKNOWN|PREV|NEXT)\n * @param force: {boolean} if TRUE - will ignore noWrap flag, else will return undefined if next slide require wrapping\n * @returns {any}\n */\n private findNextSlideIndex(direction: Direction, force: boolean): number {\n let nextSlideIndex: number = 0;\n\n if (!force && (this.isLast(this.activeSlide) && direction !== Direction.PREV && this.noWrap)) {\n return void 0;\n }\n\n switch (direction) {\n case Direction.NEXT:\n // if this is last slide, not force, looping is disabled and need to going forward - select current slide, as a next\n nextSlideIndex = (!this.isLast(this._currentActiveSlide)) ? this._currentActiveSlide + 1 :\n (!force && this.noWrap ) ? this._currentActiveSlide : 0;\n break;\n case Direction.PREV:\n // if this is first slide, not force, looping is disabled and need to going backward - select current slide, as a next\n nextSlideIndex = (this._currentActiveSlide > 0) ? this._currentActiveSlide - 1 :\n (!force && this.noWrap ) ? this._currentActiveSlide : this._slides.length - 1;\n break;\n default:\n throw new Error('Unknown direction');\n }\n return nextSlideIndex;\n }\n\n /**\n * Sets a slide, which specified through index, as active\n * @param index\n * @private\n */\n private _select(index: number): void {\n if (isNaN(index)) {\n this.pause();\n return;\n }\n let currentSlide = this._slides.get(this._currentActiveSlide);\n if (currentSlide) {\n currentSlide.active = false;\n }\n let nextSlide = this._slides.get(index);\n if (nextSlide) {\n this._currentActiveSlide = index;\n nextSlide.active = true;\n this.activeSlide = index;\n this.activeSlideChange.emit(index);\n }\n }\n\n /**\n * Starts loop of auto changing of slides\n */\n private restartTimer(): any {\n this.resetTimer();\n let interval = +this.interval;\n if (!isNaN(interval) && interval > 0) {\n this.currentInterval = setInterval(\n () => {\n let nInterval = +this.interval;\n if (this.isPlaying && !isNaN(this.interval) && nInterval > 0 && this.slides.length) {\n this.nextSlide();\n } else {\n this.pause();\n }\n },\n interval);\n }\n }\n\n /**\n * Stops loop of auto changing of slides\n */\n private resetTimer(): void {\n if (this.currentInterval) {\n clearInterval(this.currentInterval);\n this.currentInterval = void 0;\n }\n }\nstatic decorators: DecoratorInvocation[] = [\n{ type: Component, args: [{\n selector: 'carousel',\n template: `\n <div (mouseenter)=\"pause()\" (mouseleave)=\"play()\" (mouseup)=\"play()\" class=\"carousel slide\">\n <ol class=\"carousel-indicators\" *ngIf=\"slides.length > 1\">\n <li *ngFor=\"let slidez of slides; let i = index;\" [class.active]=\"slidez.active === true\" (click)=\"selectSlide(i)\"></li>\n </ol>\n <div class=\"carousel-inner\"><ng-content></ng-content></div>\n <a class=\"left carousel-control carousel-control-prev\" [class.disabled]=\"activeSlide === 0 && noWrap\" (click)=\"previousSlide()\" *ngIf=\"slides.length > 1\">\n <span class=\"icon-prev carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n <span *ngIf=\"isBs4\" class=\"sr-only\">Previous</span>\n </a>\n <a class=\"right carousel-control carousel-control-next\" (click)=\"nextSlide()\" [class.disabled]=\"isLast(activeSlide) && noWrap\" *ngIf=\"slides.length > 1\">\n <span class=\"icon-next carousel-control-next-icon\" aria-hidden=\"true\"></span>\n <span class=\"sr-only\">Next</span>\n </a>\n </div>\n `\n}, ] },\n];\n/** @nocollapse */\nstatic ctorParameters: () => ({type: any, decorators?: DecoratorInvocation[]}|null)[] = () => [\n{type: CarouselConfig, },\n];\nstatic propDecorators: {[key: string]: DecoratorInvocation[]} = {\n'noWrap': [{ type: Input },],\n'noPause': [{ type: Input },],\n'activeSlideChange': [{ type: Output },],\n'activeSlide': [{ type: Input },],\n'interval': [{ type: Input },],\n};\n}\n\ninterface DecoratorInvocation {\n type: Function;\n args?: any[];\n}\n"]}
\No newline at end of file