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);
+ });
+});