diff --git a/CHANGELOG.md b/CHANGELOG.md index 625d2446..c377cdd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 1.0.0-beta3 (19.04.2021) + +### New components: + +- [Alerts](https://mdbootstrap.com/docs/b5/angular/components/alerts/) +- [Carousel](https://mdbootstrap.com/docs/b5/angular/components/carousel) +- [Toasts](https://mdbootstrap.com/docs/b5/angular/components/toasts) + +### Bug fixes: + +- Datepicker - resolved problem with keyboard navigation when using `DownArrow` key, +- Datepicker - resolved problem with selecting dates using `Enter/Space` keys in component with date filter, +- Datepicker - added correct aria-labels to the previous/next buttons in the days view. + +--- + ## 1.0.0-beta2 (06.04.2021) ### New components: diff --git a/README.txt b/README.txt index 06e04be5..e4157518 100644 --- a/README.txt +++ b/README.txt @@ -1,6 +1,6 @@ MDB 5 Angular -Version: FREE 1.0.0 Beta 2 +Version: FREE 1.0.0 Beta 3 Documentation: https://mdbootstrap.com/docs/b5/angular/ diff --git a/package.json b/package.json index f3f73763..7093d495 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mdb-angular-ui-kit-free", - "version": "1.0.0-beta2", + "version": "1.0.0-beta3", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/projects/mdb-angular-ui-kit/CHANGELOG.md b/projects/mdb-angular-ui-kit/CHANGELOG.md index 625d2446..c377cdd7 100644 --- a/projects/mdb-angular-ui-kit/CHANGELOG.md +++ b/projects/mdb-angular-ui-kit/CHANGELOG.md @@ -1,3 +1,19 @@ +## 1.0.0-beta3 (19.04.2021) + +### New components: + +- [Alerts](https://mdbootstrap.com/docs/b5/angular/components/alerts/) +- [Carousel](https://mdbootstrap.com/docs/b5/angular/components/carousel) +- [Toasts](https://mdbootstrap.com/docs/b5/angular/components/toasts) + +### Bug fixes: + +- Datepicker - resolved problem with keyboard navigation when using `DownArrow` key, +- Datepicker - resolved problem with selecting dates using `Enter/Space` keys in component with date filter, +- Datepicker - added correct aria-labels to the previous/next buttons in the days view. + +--- + ## 1.0.0-beta2 (06.04.2021) ### New components: diff --git a/projects/mdb-angular-ui-kit/LICENSE b/projects/mdb-angular-ui-kit/LICENSE new file mode 100644 index 00000000..64ef5206 --- /dev/null +++ b/projects/mdb-angular-ui-kit/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 MDBootstrap + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/projects/mdb-angular-ui-kit/License.pdf b/projects/mdb-angular-ui-kit/License.pdf deleted file mode 100644 index 9db6ccc2..00000000 Binary files a/projects/mdb-angular-ui-kit/License.pdf and /dev/null differ diff --git a/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_carousel.scss b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_carousel.scss index a61404d5..c398669f 100644 --- a/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_carousel.scss +++ b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_carousel.scss @@ -96,8 +96,11 @@ align-items: center; // 2. vertically center contents justify-content: center; // 3. horizontally center contents width: $carousel-control-width; + padding: 0; color: $carousel-control-color; text-align: center; + background: none; + border: 0; opacity: $carousel-control-opacity; @include transition($carousel-control-transition); @@ -153,10 +156,10 @@ background-image: escape-svg($carousel-control-next-icon-bg); } -// Optional indicator pips +// Optional indicator pips/controls // -// Add an ordered list with the following class and add a list item for each -// slide your carousel holds. +// Add a container (such as a list) with the following class and add an item (ideally a focusable control, +// like a button) with data-bs-target for each slide your carousel holds. .carousel-indicators { position: absolute; @@ -166,23 +169,26 @@ z-index: 2; display: flex; justify-content: center; - padding-left: 0; // override
    default + padding: 0; // Use the .carousel-control's width as margin so we don't overlay those margin-right: $carousel-control-width; + margin-bottom: 1rem; margin-left: $carousel-control-width; list-style: none; - li { + button { box-sizing: content-box; flex: 0 1 auto; width: $carousel-indicator-width; height: $carousel-indicator-height; + padding: 0; margin-right: $carousel-indicator-spacer; margin-left: $carousel-indicator-spacer; text-indent: -999px; cursor: pointer; background-color: $carousel-indicator-active-bg; background-clip: padding-box; + border: 0; // Use transparent borders to increase the hit area by 10px on top and bottom. border-top: $carousel-indicator-hit-area-height solid transparent; border-bottom: $carousel-indicator-hit-area-height solid transparent; @@ -218,7 +224,7 @@ filter: $carousel-dark-control-icon-filter; } - .carousel-indicators li { + .carousel-indicators button { background-color: $carousel-dark-indicator-active-bg; } diff --git a/projects/mdb-angular-ui-kit/assets/scss/free/_alert.scss b/projects/mdb-angular-ui-kit/assets/scss/free/_alert.scss new file mode 100644 index 00000000..47cc3357 --- /dev/null +++ b/projects/mdb-angular-ui-kit/assets/scss/free/_alert.scss @@ -0,0 +1,18 @@ +// Alert + +.alert { + border: 0; +} + +.alert-absolute { + position: absolute; +} + +.alert-fixed { + position: fixed; + z-index: $zindex-alert; +} + +.parent-alert-relative { + position: relative; +} diff --git a/projects/mdb-angular-ui-kit/assets/scss/free/_carousel.scss b/projects/mdb-angular-ui-kit/assets/scss/free/_carousel.scss new file mode 100644 index 00000000..bc38b4cc --- /dev/null +++ b/projects/mdb-angular-ui-kit/assets/scss/free/_carousel.scss @@ -0,0 +1,25 @@ +mdb-carousel { + display: block; +} + +.carousel-control-prev-icon { + &::after { + content: '\f053'; + font-weight: $font-weight-bold; + font-family: 'Font Awesome 5 Pro', 'Font Awesome 5 Free'; + font-size: 1.7rem; + } +} +.carousel-control-next-icon { + &::after { + content: '\f054'; + font-weight: $font-weight-bold; + font-family: 'Font Awesome 5 Pro', 'Font Awesome 5 Free'; + font-size: 1.7rem; + } +} +.carousel-indicators { + [data-mdb-target] { + @extend [data-bs-target] !optional; + } +} diff --git a/projects/mdb-angular-ui-kit/assets/scss/free/_toasts.scss b/projects/mdb-angular-ui-kit/assets/scss/free/_toasts.scss new file mode 100644 index 00000000..767b7bfa --- /dev/null +++ b/projects/mdb-angular-ui-kit/assets/scss/free/_toasts.scss @@ -0,0 +1,32 @@ +// Toasts + +.toast { + background-color: $toast-background-color; + border: 0; + box-shadow: $toast-box-shadow; + + .btn-close { + width: 1.3em; + } +} + +.toast-header { + background-color: $toast-header-background-color; +} + +.parent-toast-relative { + position: relative; +} + +.toast-absolute { + position: absolute; +} + +.toast-fixed { + position: fixed; + z-index: $zindex-toast; +} + +.toast:not(.showing):not(.show) { + opacity: 1; +} diff --git a/projects/mdb-angular-ui-kit/assets/scss/mdb.free.scss b/projects/mdb-angular-ui-kit/assets/scss/mdb.free.scss index 8e7ff4df..36b5fcdb 100644 --- a/projects/mdb-angular-ui-kit/assets/scss/mdb.free.scss +++ b/projects/mdb-angular-ui-kit/assets/scss/mdb.free.scss @@ -80,6 +80,8 @@ @import './free/popover'; @import './free/dropdown'; @import './free/range'; +@import './free/alert'; +@import './free/toasts'; // MDB FORMS @import './free/forms/form-check'; diff --git a/projects/mdb-angular-ui-kit/assets/scss/mdb.scss b/projects/mdb-angular-ui-kit/assets/scss/mdb.scss index e45c03aa..647a9c8c 100644 --- a/projects/mdb-angular-ui-kit/assets/scss/mdb.scss +++ b/projects/mdb-angular-ui-kit/assets/scss/mdb.scss @@ -70,6 +70,7 @@ @import './free/nav'; @import './free/navbar'; @import './free/card'; +@import './free/carousel'; @import './free/breadcrumb'; @import './free/pagination'; @import './free/badge'; @@ -84,6 +85,8 @@ @import './free/validation'; @import './free/scrollspy'; @import './free/range'; +@import './free/alert'; +@import './free/toasts'; // MDB FORMS @import './free/forms/form-check'; diff --git a/projects/mdb-angular-ui-kit/carousel/carousel-item.component.ts b/projects/mdb-angular-ui-kit/carousel/carousel-item.component.ts new file mode 100644 index 00000000..49a0533b --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/carousel-item.component.ts @@ -0,0 +1,28 @@ +import { Component, ElementRef, HostBinding, Input, OnInit } from '@angular/core'; + +@Component({ + // tslint:disable-next-line: component-selector + selector: 'mdb-carousel-item', + template: '', +}) +export class MdbCarouselItemComponent implements OnInit { + @Input() interval: number | null = null; + + @HostBinding('class.carousel-item') + carouselItem = true; + + @HostBinding('class.active') active = false; + + @HostBinding('class.carousel-item-next') next = false; + @HostBinding('class.carousel-item-prev') prev = false; + @HostBinding('class.carousel-item-start') start = false; + @HostBinding('class.carousel-item-end') end = false; + + get host(): HTMLElement { + return this._elementRef.nativeElement; + } + + constructor(private _elementRef: ElementRef) {} + + ngOnInit(): void {} +} diff --git a/projects/mdb-angular-ui-kit/carousel/carousel.component.html b/projects/mdb-angular-ui-kit/carousel/carousel.component.html new file mode 100644 index 00000000..fbd79a1b --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/carousel.component.html @@ -0,0 +1,28 @@ + diff --git a/projects/mdb-angular-ui-kit/carousel/carousel.component.ts b/projects/mdb-angular-ui-kit/carousel/carousel.component.ts new file mode 100644 index 00000000..2174ecf7 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/carousel.component.ts @@ -0,0 +1,360 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChildren, + ElementRef, + EventEmitter, + HostListener, + Input, + OnDestroy, + Output, + QueryList, +} from '@angular/core'; +import { fromEvent, Subject } from 'rxjs'; +import { take, takeUntil } from 'rxjs/operators'; +import { MdbCarouselItemComponent } from './carousel-item.component'; + +export enum Direction { + UNKNOWN, + NEXT, + PREV, +} + +@Component({ + // tslint:disable-next-line: component-selector + selector: 'mdb-carousel', + templateUrl: './carousel.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MdbCarouselComponent implements AfterViewInit, OnDestroy { + @ContentChildren(MdbCarouselItemComponent) _items: QueryList; + get items(): MdbCarouselItemComponent[] { + return this._items && this._items.toArray(); + } + + @Input() animation: 'slide' | 'fade' = 'slide'; + @Input() controls = false; + @Input() dark = false; + @Input() indicators = false; + @Input() ride = true; + + @Input() + get interval(): number { + return this._interval; + } + set interval(value: number) { + this._interval = value; + + if (this.items) { + this._restartInterval(); + } + } + private _interval = 5000; + + @Input() keyboard = true; + @Input() pause = true; + @Input() wrap = true; + + @Output() slide: EventEmitter = new EventEmitter(); + @Output() slideChange: EventEmitter = new EventEmitter(); + + get activeSlide(): number { + return this._activeSlide; + } + + set activeSlide(index: number) { + if (this.items.length && this._activeSlide !== index) { + this._activeSlide = index; + this._restartInterval(); + } + } + private _activeSlide = 0; + + private _lastInterval: any; + private _isPlaying = false; + private _isSliding = false; + + private readonly _destroy$: Subject = new Subject(); + + @HostListener('mouseenter') + onMouseEnter(): void { + if (this.pause && this._isPlaying) { + this.stop(); + } + } + + @HostListener('mouseleave') + onMouseLeave(): void { + if (this.pause && !this._isPlaying) { + this.play(); + } + } + + constructor(private _elementRef: ElementRef, private _cdRef: ChangeDetectorRef) {} + + ngAfterViewInit(): void { + Promise.resolve().then(() => { + this._setActiveSlide(this._activeSlide); + + if (this.interval > 0 && this.ride) { + this.play(); + } + }); + + if (this.keyboard) { + fromEvent(this._elementRef.nativeElement, 'keydown') + .pipe(takeUntil(this._destroy$)) + // tslint:disable-next-line: deprecation + .subscribe((event: KeyboardEvent) => { + if (event.key === 'ArrowRight') { + this.next(); + } else if (event.key === 'ArrowLeft') { + this.prev(); + } + }); + } + } + + ngOnDestroy(): void { + this._destroy$.next(); + this._destroy$.complete(); + } + + private _setActiveSlide(index: number): void { + const currentSlide = this.items[this._activeSlide]; + currentSlide.active = false; + + const newSlide = this.items[index]; + newSlide.active = true; + this._activeSlide = index; + } + + private _restartInterval(): void { + this._resetInterval(); + const activeElement = this.items[this.activeSlide]; + const interval = activeElement.interval ? activeElement.interval : this.interval; + + if (!isNaN(interval) && interval > 0) { + this._lastInterval = setInterval(() => { + const nInterval = +interval; + if (this._isPlaying && !isNaN(nInterval) && nInterval > 0) { + this.next(); + } else { + this.stop(); + } + }, interval); + } + } + + private _resetInterval(): void { + if (this._lastInterval) { + clearInterval(this._lastInterval); + this._lastInterval = null; + } + } + + play(): void { + if (!this._isPlaying) { + this._isPlaying = true; + this._restartInterval(); + } + } + + stop(): void { + if (this._isPlaying) { + this._isPlaying = false; + this._resetInterval(); + } + } + + to(index: number): void { + if (index > this.items.length - 1 || index < 0) { + return; + } + + if (this.activeSlide === index) { + this.stop(); + this.play(); + return; + } + + const direction = index > this.activeSlide ? Direction.NEXT : Direction.PREV; + + this._animateSlides(direction, this.activeSlide, index); + this.activeSlide = index; + + this._cdRef.markForCheck(); + } + + next(): void { + if (!this._isSliding) { + this._slide(Direction.NEXT); + } + + this._cdRef.markForCheck(); + } + + prev(): void { + if (!this._isSliding) { + this._slide(Direction.PREV); + } + + this._cdRef.markForCheck(); + } + + private _slide(direction: Direction): void { + const isFirst = this._activeSlide === 0; + const isLast = this._activeSlide === this.items.length - 1; + + if (!this.wrap) { + if ((direction === Direction.NEXT && isLast) || (direction === Direction.PREV && isFirst)) { + return; + } + } + + const newSlideIndex = this._getNewSlideIndex(direction); + + this._animateSlides(direction, this.activeSlide, newSlideIndex); + this.activeSlide = newSlideIndex; + + this.slide.emit(); + } + + private _animateSlides(direction: Direction, currentIndex: number, nextIndex: number): void { + const currentItem = this.items[currentIndex]; + const nextItem = this.items[nextIndex]; + const currentEl = currentItem.host; + const nextEl = nextItem.host; + + this._isSliding = true; + + if (this._isPlaying) { + this.stop(); + } + + if (direction === Direction.NEXT) { + nextItem.next = true; + + setTimeout(() => { + this._reflow(nextEl); + currentItem.start = true; + nextItem.start = true; + }, 0); + + const transitionDuration = 600; + + fromEvent(currentEl, 'transitionend') + .pipe(take(1)) + // tslint:disable-next-line: deprecation + .subscribe(() => { + nextItem.next = false; + nextItem.start = false; + nextItem.active = true; + + currentItem.active = false; + currentItem.start = false; + currentItem.next = false; + + this.slideChange.emit(); + this._isSliding = false; + }); + + this._emulateTransitionEnd(currentEl, transitionDuration); + } else if (direction === Direction.PREV) { + nextItem.prev = true; + + setTimeout(() => { + this._reflow(nextEl); + currentItem.end = true; + nextItem.end = true; + }, 0); + + const transitionDuration = 600; + + fromEvent(currentEl, 'transitionend') + .pipe(take(1)) + // tslint:disable-next-line: deprecation + .subscribe(() => { + nextItem.prev = false; + nextItem.end = false; + nextItem.active = true; + + currentItem.active = false; + currentItem.end = false; + currentItem.prev = false; + + this.slideChange.emit(); + this._isSliding = false; + }); + + this._emulateTransitionEnd(currentEl, transitionDuration); + } + + if (!this._isPlaying && this.interval > 0) { + this.play(); + } + } + + private _reflow(element: HTMLElement): number { + return element.offsetHeight; + } + + private _emulateTransitionEnd(element: HTMLElement, duration: number): void { + let eventEmitted = false; + const durationPadding = 5; + const emulatedDuration = duration + durationPadding; + + fromEvent(element, 'transitionend') + .pipe(take(1)) + // tslint:disable-next-line: deprecation + .subscribe(() => { + eventEmitted = true; + }); + + setTimeout(() => { + if (!eventEmitted) { + element.dispatchEvent(new Event('transitionend')); + } + }, emulatedDuration); + } + + private _getNewSlideIndex(direction: Direction): number { + let newSlideIndex: number; + + if (direction === Direction.NEXT) { + newSlideIndex = this._getNextSlideIndex(); + } + + if (direction === Direction.PREV) { + newSlideIndex = this._getPrevSlideIndex(); + } + + return newSlideIndex; + } + + private _getNextSlideIndex(): number { + const isLast = this._activeSlide === this.items.length - 1; + + if (!isLast) { + return this._activeSlide + 1; + } else if (this.wrap && isLast) { + return 0; + } else { + return this._activeSlide; + } + } + + private _getPrevSlideIndex(): number { + const isFirst = this._activeSlide === 0; + + if (!isFirst) { + return this._activeSlide - 1; + } else if (this.wrap && isFirst) { + return this.items.length - 1; + } else { + return this._activeSlide; + } + } +} diff --git a/projects/mdb-angular-ui-kit/carousel/carousel.module.ts b/projects/mdb-angular-ui-kit/carousel/carousel.module.ts new file mode 100644 index 00000000..67a70093 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/carousel.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { MdbCarouselComponent } from './carousel.component'; +import { MdbCarouselItemComponent } from './carousel-item.component'; + +@NgModule({ + declarations: [MdbCarouselComponent, MdbCarouselItemComponent], + exports: [MdbCarouselComponent, MdbCarouselItemComponent], + imports: [CommonModule], +}) +export class MdbCarouselModule {} diff --git a/projects/mdb-angular-ui-kit/carousel/carousel.spec.ts b/projects/mdb-angular-ui-kit/carousel/carousel.spec.ts new file mode 100644 index 00000000..561ba956 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/carousel.spec.ts @@ -0,0 +1,216 @@ +import { Component, ViewChild } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; +import { MdbCarouselComponent } from './carousel.component'; +import { MdbCarouselModule } from './carousel.module'; + +const carouselTemplate = ` + + + ... + + + + ... + + + + ... + + +`; + +@Component({ + template: carouselTemplate, +}) +export class CarouselTestComponent { + @ViewChild(MdbCarouselComponent, { static: true }) carousel: MdbCarouselComponent; + controls = false; + indicators = false; + wrap = true; + dark = false; + animation = 'slide'; +} + +describe('MDB Carousel', () => { + let fixture: ComponentFixture; + let component: CarouselTestComponent; + let carousel: MdbCarouselComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [CarouselTestComponent], + imports: [MdbCarouselModule], + }); + + fixture = TestBed.createComponent(CarouselTestComponent); + component = fixture.componentInstance; + carousel = component.carousel; + + fixture.detectChanges(); + }); + + it('should set first slide as active by default', fakeAsync(() => { + flush(); + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + expect(items[0].classList.contains('active')).toBe(true); + })); + + it('should show indicators if indicators input is set to true', () => { + component.indicators = true; + fixture.detectChanges(); + const indicators = fixture.nativeElement.querySelectorAll('.carousel-indicators'); + expect(indicators).toBeDefined(); + }); + + it('should show controls if controls input is set to true', () => { + component.controls = true; + fixture.detectChanges(); + const prevArrow = fixture.nativeElement.querySelector('.carousel-control-prev'); + const nextArrow = fixture.nativeElement.querySelector('.carousel-control-next'); + expect(prevArrow).toBeDefined(); + expect(nextArrow).toBeDefined(); + }); + + it('should add carousel-fade class if animation type is set to fade', () => { + component.animation = 'fade'; + fixture.detectChanges(); + const carouselEl = fixture.nativeElement.querySelector('.carousel'); + expect(carouselEl.classList.contains('carousel-fade')).toBe(true); + }); + + it('should add carousel-dark class if dark input is set to true', () => { + component.dark = true; + fixture.detectChanges(); + const carouselEl = fixture.nativeElement.querySelector('.carousel'); + expect(carouselEl.classList.contains('carousel-dark')).toBe(true); + }); + + it('should set corresponding indicator as active', fakeAsync(() => { + component.indicators = true; + fixture.detectChanges(); + const indicators = fixture.nativeElement.querySelectorAll('.carousel-indicators > button'); + expect(indicators[0].classList.contains('active')).toBe(true); + })); + + it('should change active slide on indicator click', fakeAsync(() => { + component.indicators = true; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const indicators = fixture.nativeElement.querySelectorAll('.carousel-indicators > button'); + expect(indicators[0].classList.contains('active')).toBe(true); + expect(items[0].classList.contains('active')).toBe(true); + + indicators[1].click(); + tick(1000); + fixture.detectChanges(); + + expect(indicators[1].classList.contains('active')).toBe(true); + expect(items[1].classList.contains('active')).toBe(true); + })); + + it('should change slide on previous arrow click', fakeAsync(() => { + component.controls = true; + component.wrap = true; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const prevArrow = fixture.nativeElement.querySelector('.carousel-control-prev'); + expect(items[0].classList.contains('active')).toBe(true); + + prevArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[2].classList.contains('active')).toBe(true); + + prevArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[1].classList.contains('active')).toBe(true); + })); + + it('should change slide on next arrow click', fakeAsync(() => { + component.controls = true; + component.wrap = true; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const nextArrow = fixture.nativeElement.querySelector('.carousel-control-next'); + expect(items[0].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[1].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[2].classList.contains('active')).toBe(true); + })); + + it('should not go to previous slide if first slide is active and wrap option is disabled', fakeAsync(() => { + component.controls = true; + component.wrap = false; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const prevArrow = fixture.nativeElement.querySelector('.carousel-control-prev'); + expect(items[0].classList.contains('active')).toBe(true); + + prevArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[0].classList.contains('active')).toBe(true); + expect(items[2].classList.contains('active')).toBe(false); + })); + + it('should not go to next slide if last slide is active and wrap option is disabled', fakeAsync(() => { + component.controls = true; + component.wrap = false; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const nextArrow = fixture.nativeElement.querySelector('.carousel-control-next'); + expect(items[0].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[1].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[2].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[2].classList.contains('active')).toBe(true); + expect(items[0].classList.contains('active')).toBe(false); + })); +}); diff --git a/projects/mdb-angular-ui-kit/carousel/index.ts b/projects/mdb-angular-ui-kit/carousel/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/carousel/public_api.ts b/projects/mdb-angular-ui-kit/carousel/public_api.ts new file mode 100644 index 00000000..0c661600 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/public_api.ts @@ -0,0 +1,3 @@ +export { MdbCarouselComponent } from './carousel.component'; +export { MdbCarouselItemComponent } from './carousel-item.component'; +export { MdbCarouselModule } from './carousel.module'; diff --git a/projects/mdb-angular-ui-kit/index.ts b/projects/mdb-angular-ui-kit/index.ts index d832aecd..34f92c9e 100644 --- a/projects/mdb-angular-ui-kit/index.ts +++ b/projects/mdb-angular-ui-kit/index.ts @@ -15,6 +15,7 @@ import { MdbValidationModule } from './validation/validation.module'; import { MdbScrollspyModule } from './scrollspy/scrollspy.module'; import { MdbRangeModule } from './range/range.module'; import { MdbTabsModule } from './tabs/tabs.module'; +import { MdbCarouselModule } from './carousel/carousel.module'; export { MdbCollapseDirective, MdbCollapseModule } from './collapse/index'; export { @@ -72,6 +73,11 @@ export { MdbTabsComponent, MdbTabsModule, } from './tabs/index'; +export { + MdbCarouselComponent, + MdbCarouselItemComponent, + MdbCarouselModule, +} from './carousel/index'; const MDB_MODULES = [ MdbCollapseModule, @@ -87,6 +93,7 @@ const MDB_MODULES = [ MdbScrollspyModule, MdbRangeModule, MdbTabsModule, + MdbCarouselModule, ]; @NgModule({ diff --git a/projects/mdb-angular-ui-kit/package.json b/projects/mdb-angular-ui-kit/package.json index cad94330..b2642a78 100644 --- a/projects/mdb-angular-ui-kit/package.json +++ b/projects/mdb-angular-ui-kit/package.json @@ -3,7 +3,7 @@ "repository": "https://github.com/mdbootstrap/mdb-angular-ui-kit", "author": "MDBootstrap", "license": "MIT", - "version": "1.0.0-beta2", + "version": "1.0.0-beta3", "peerDependencies": { "@angular/common": "^11.0.0", "@angular/core": "^11.0.0", diff --git a/projects/mdb-angular-ui-kit/tabs/tabs.spec.ts b/projects/mdb-angular-ui-kit/tabs/tabs.spec.ts index 79e0e5cb..1f92a0fe 100644 --- a/projects/mdb-angular-ui-kit/tabs/tabs.spec.ts +++ b/projects/mdb-angular-ui-kit/tabs/tabs.spec.ts @@ -25,7 +25,7 @@ export class TabsTestComponent { @ViewChildren(MdbTabComponent) tabComponents: QueryList; } -describe('NbTabsetComponent', () => { +describe('MDB Tabs', () => { let fixture: ComponentFixture; let component: TabsTestComponent; let tabsComponent: MdbTabsComponent;