From e3103f07860b6d57ebf32155fd3ae416afd3e386 Mon Sep 17 00:00:00 2001 From: Yurica Xu Date: Mon, 7 Nov 2022 10:23:39 +0800 Subject: [PATCH] feat(module: carousel): `nzLoop` to prevent the carousel to go in a loop (#7693) * feat(module: carousel): `nzLoop` to prevent the carousel to go in a loop * ci(module: carousel): improve test coverage --- components/carousel/carousel.component.ts | 15 ++++++++-- components/carousel/carousel.spec.ts | 31 +++++++++++++++++++++ components/carousel/demo/loop.md | 14 ++++++++++ components/carousel/demo/loop.ts | 34 +++++++++++++++++++++++ components/carousel/doc/index.en-US.md | 1 + components/carousel/doc/index.zh-CN.md | 1 + components/core/config/config.ts | 1 + components/core/util/text-mesure.spec.ts | 7 +++++ 8 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 components/carousel/demo/loop.md create mode 100644 components/carousel/demo/loop.ts create mode 100644 components/core/util/text-mesure.spec.ts diff --git a/components/carousel/carousel.component.ts b/components/carousel/carousel.component.ts index 2e658f619a..c53dc8199f 100644 --- a/components/carousel/carousel.component.ts +++ b/components/carousel/carousel.component.ts @@ -127,6 +127,7 @@ export class NzCarouselComponent implements AfterContentInit, AfterViewInit, OnD @Input() @WithConfig() @InputBoolean() nzAutoPlay: boolean = false; @Input() @WithConfig() @InputNumber() nzAutoPlaySpeed: number = 3000; @Input() @InputNumber() nzTransitionSpeed = 500; + @Input() @WithConfig() nzLoop: boolean = true; /** * this property is passed directly to an NzCarouselBaseStrategy @@ -300,7 +301,12 @@ export class NzCarouselComponent implements AfterContentInit, AfterViewInit, OnD } goTo(index: number): void { - if (this.carouselContents && this.carouselContents.length && !this.isTransiting) { + if ( + this.carouselContents && + this.carouselContents.length && + !this.isTransiting && + (this.nzLoop || (index >= 0 && index < this.carouselContents.length)) + ) { const length = this.carouselContents.length; const from = this.activeIndex; const to = (index + length) % length; @@ -386,7 +392,12 @@ export class NzCarouselComponent implements AfterContentInit, AfterViewInit, OnD const xDelta = this.pointerDelta ? this.pointerDelta.x : 0; // Switch to another slide if delta is bigger than third of the width. - if (Math.abs(xDelta) > this.gestureRect!.width / 3) { + if ( + Math.abs(xDelta) > this.gestureRect!.width / 3 && + (this.nzLoop || + (xDelta <= 0 && this.activeIndex + 1 < this.carouselContents.length) || + (xDelta > 0 && this.activeIndex > 0)) + ) { this.goTo(xDelta > 0 ? this.activeIndex - 1 : this.activeIndex + 1); } else { this.goTo(this.activeIndex); diff --git a/components/carousel/carousel.spec.ts b/components/carousel/carousel.spec.ts index 3b079b67ff..c586d4f8e8 100644 --- a/components/carousel/carousel.spec.ts +++ b/components/carousel/carousel.spec.ts @@ -220,6 +220,35 @@ describe('carousel', () => { tickMilliseconds(fixture, 700); expect(carouselContents[1].nativeElement.classList).toContain('slick-active'); })); + + it('should disable loop work', fakeAsync(() => { + testComponent.loop = false; + fixture.detectChanges(); + swipe(testComponent.nzCarouselComponent, -10); + tickMilliseconds(fixture, 700); + expect(carouselContents[0].nativeElement.classList).toContain('slick-active'); + swipe(testComponent.nzCarouselComponent, -1000); + tickMilliseconds(fixture, 700); + expect(carouselContents[0].nativeElement.classList).toContain('slick-active'); + + testComponent.loop = true; + fixture.detectChanges(); + swipe(testComponent.nzCarouselComponent, -1000); + tickMilliseconds(fixture, 700); + expect(carouselContents[3].nativeElement.classList).toContain('slick-active'); + swipe(testComponent.nzCarouselComponent, 1000); + tickMilliseconds(fixture, 700); + expect(carouselContents[0].nativeElement.classList).toContain('slick-active'); + + testComponent.loop = false; + testComponent.autoPlay = true; + testComponent.autoPlaySpeed = 1000; + fixture.detectChanges(); + tick(10000); + expect(carouselContents[3].nativeElement.classList).toContain('slick-active'); + tick(1000 + 10); + expect(carouselContents[3].nativeElement.classList).toContain('slick-active'); + })); }); describe('strategies', () => { @@ -406,6 +435,7 @@ function swipe(carousel: NzCarouselComponent, distance: number): void { [nzDotRender]="dotRender" [nzAutoPlay]="autoPlay" [nzAutoPlaySpeed]="autoPlaySpeed" + [nzLoop]="loop" (nzAfterChange)="afterChange($event)" (nzBeforeChange)="beforeChange($event)" > @@ -426,6 +456,7 @@ export class NzTestCarouselBasicComponent { array = [1, 2, 3, 4]; autoPlay = false; autoPlaySpeed = 3000; + loop = true; afterChange = jasmine.createSpy('afterChange callback'); beforeChange = jasmine.createSpy('beforeChange callback'); } diff --git a/components/carousel/demo/loop.md b/components/carousel/demo/loop.md new file mode 100644 index 0000000000..e250e6d9ed --- /dev/null +++ b/components/carousel/demo/loop.md @@ -0,0 +1,14 @@ +--- +order: 5 +title: + zh-CN: 循环 + en-US: Loop +--- + +## zh-CN + +防止轮播进入循环 + +## en-US + +Prevent the carousel to go in a loop \ No newline at end of file diff --git a/components/carousel/demo/loop.ts b/components/carousel/demo/loop.ts new file mode 100644 index 0000000000..76e61b87ec --- /dev/null +++ b/components/carousel/demo/loop.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-carousel-loop', + template: ` + +
+

{{ index }}

+
+
+ `, + styles: [ + ` + [nz-carousel-content] { + text-align: center; + height: 160px; + line-height: 160px; + background: #364d79; + color: #fff; + overflow: hidden; + } + + h3 { + color: #fff; + margin-bottom: 0; + user-select: none; + } + ` + ] +}) +export class NzDemoCarouselLoopComponent { + array = [1, 2, 3, 4]; + effect = 'scrollx'; +} diff --git a/components/carousel/doc/index.en-US.md b/components/carousel/doc/index.en-US.md index ade299dd6c..3ec2ee912c 100644 --- a/components/carousel/doc/index.en-US.md +++ b/components/carousel/doc/index.en-US.md @@ -30,6 +30,7 @@ import { NzCarouselModule } from 'ng-zorro-antd/carousel'; | `[nzDots]` | Whether to show the dots at the bottom of the gallery | `boolean` | `true` | ✅ | | `[nzEffect]` | Transition effect | `'scrollx'\|'fade'` | `'scrollx'` | ✅ | | `[nzEnableSwipe]` | Whether to support swipe gesture | `boolean` | `true` | ✅ | +| `[nzLoop]` | Whether to enable the carousel to go in a loop | `boolean` | `true` | ✅ | | `(nzAfterChange)` | Callback function called after the current index changes | `EventEmitter` | - | | `(nzBeforeChange)` | Callback function called before the current index changes | `EventEmitter{ from: number; to: number }>` | - | diff --git a/components/carousel/doc/index.zh-CN.md b/components/carousel/doc/index.zh-CN.md index 77cce7cc76..93e44737a0 100644 --- a/components/carousel/doc/index.zh-CN.md +++ b/components/carousel/doc/index.zh-CN.md @@ -31,6 +31,7 @@ import { NzCarouselModule } from 'ng-zorro-antd/carousel'; | `[nzDots]` | 是否显示面板指示点 | `boolean` | `true` | ✅ | | `[nzEffect]` | 动画效果函数,可取 `scrollx`, `fade` | `'scrollx'\|'fade'`|`'scrollx'` | ✅ | | `[nzEnableSwipe]` | 是否支持手势划动切换 | `boolean` | `true` | ✅ | +| `[nzLoop]` | 是否支持循环 | `boolean` | `true` | ✅ | | `(nzAfterChange)` | 切换面板的回调 | `EventEmitter` | - | | `(nzBeforeChange)` | 切换面板的回调 | `EventEmitter<{ from: number; to: number }>` | - | diff --git a/components/core/config/config.ts b/components/core/config/config.ts index 7778d1dbc8..7286808f50 100644 --- a/components/core/config/config.ts +++ b/components/core/config/config.ts @@ -160,6 +160,7 @@ export interface CarouselConfig { nzEffect?: 'scrollx' | 'fade' | string; nzEnableSwipe?: boolean; nzVertical?: boolean; + nzLoop?: boolean; } export interface CascaderConfig { diff --git a/components/core/util/text-mesure.spec.ts b/components/core/util/text-mesure.spec.ts new file mode 100644 index 0000000000..9fee6b7f8c --- /dev/null +++ b/components/core/util/text-mesure.spec.ts @@ -0,0 +1,7 @@ +import { pxToNumber } from './text-measure'; + +describe('pxToNumber', () => { + it('should return 0 when value is null', () => { + expect(pxToNumber(null)).toBe(0); + }); +});