From 41b56e4072b6d5832c1f9e31196dcce4fe8632aa Mon Sep 17 00:00:00 2001 From: Xu Chao <304093931@qq.com> Date: Tue, 25 Oct 2022 11:37:36 +0800 Subject: [PATCH 1/8] fix(module:date-picker): arrow in wrong position for RTL direction (#7690) --- .../date-picker/date-range-popup.component.ts | 8 ++++++- .../range-picker.component.spec.ts | 24 +++++++++++++++++++ components/date-picker/style/patch.less | 5 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/components/date-picker/date-range-popup.component.ts b/components/date-picker/date-range-popup.component.ts index c463c7dc505..377747c562e 100644 --- a/components/date-picker/date-range-popup.component.ts +++ b/components/date-picker/date-range-popup.component.ts @@ -56,7 +56,7 @@ import { getTimeConfig, isAllowedDate, PREFIX_CLASS } from './util'; template: `
-
+
@@ -179,6 +179,12 @@ export class DateRangePopupComponent implements OnInit, OnChanges, OnDestroy { return this.showToday || this.hasTimePicker || !!this.extraFooter || !!this.ranges; } + get arrowPosition(): { left?: string; right?: string } { + return this.dir === 'rtl' + ? { right: `${this.datePickerService?.arrowLeft}px` } + : { left: `${this.datePickerService?.arrowLeft}px` }; + } + constructor( public datePickerService: DatePickerService, public cdr: ChangeDetectorRef, diff --git a/components/date-picker/range-picker.component.spec.ts b/components/date-picker/range-picker.component.spec.ts index 18f8498a1d6..b41c9bb7b9b 100644 --- a/components/date-picker/range-picker.component.spec.ts +++ b/components/date-picker/range-picker.component.spec.ts @@ -410,6 +410,30 @@ describe('NzRangePickerComponent', () => { const result = (nzOnChange.calls.allArgs()[0] as Date[][])[0]; expect((result[0] as Date).getDate()).toBe(+leftText); })); + + it('should support correct position for top arrow', fakeAsync(() => { + fixture.detectChanges(); + openPickerByClickTrigger(); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const arrow = queryFromOverlay(`.${PREFIX_CLASS}-range-arrow`) as HTMLElement; + + expect(arrow.style.left).not.toBe(''); + })); + + it('should support dir rtl for top arrow', fakeAsync(() => { + fixture.debugElement.nativeElement.parentElement.setAttribute('dir', 'rtl'); + fixture.detectChanges(); + openPickerByClickTrigger(); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const arrow = queryFromOverlay(`.${PREFIX_CLASS}-range-arrow`) as HTMLElement; + + expect(arrow.style.right).not.toBe(''); + fixture.debugElement.nativeElement.parentElement.setAttribute('dir', ''); + })); }); // /general api testing describe('panel switch and move forward/afterward', () => { diff --git a/components/date-picker/style/patch.less b/components/date-picker/style/patch.less index 4de5d8e3ac4..e60750e6a3f 100644 --- a/components/date-picker/style/patch.less +++ b/components/date-picker/style/patch.less @@ -26,3 +26,8 @@ width: inherit; } } + +// make arrow in the right position in right direction +.@{picker-prefix-cls}-range-arrow { + margin-right: @input-padding-horizontal-base * 1.5; +} \ No newline at end of file From bec3b42bdbdad31dcb66000376e40b9528f68ba5 Mon Sep 17 00:00:00 2001 From: originRing <48777107+OriginRing@users.noreply.github.com> Date: Mon, 7 Nov 2022 10:23:10 +0800 Subject: [PATCH 2/8] fix(module:descriptions): nzStringTemplateOutlet title style error (#7704) fix #7698 --- components/descriptions/descriptions.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/descriptions/descriptions.component.ts b/components/descriptions/descriptions.component.ts index 50c80b5433f..3ae63c01ff7 100644 --- a/components/descriptions/descriptions.component.ts +++ b/components/descriptions/descriptions.component.ts @@ -80,7 +80,7 @@ const defaultColumnMap: { [key in NzBreakpointEnum]: number } = { - + {{ item.title }} From e3103f07860b6d57ebf32155fd3ae416afd3e386 Mon Sep 17 00:00:00 2001 From: Yurica Xu Date: Mon, 7 Nov 2022 10:23:39 +0800 Subject: [PATCH 3/8] 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 2e658f619ad..c53dc8199f4 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 3b079b67ff8..c586d4f8e88 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 00000000000..e250e6d9edc --- /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 00000000000..76e61b87ec7 --- /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 ade299dd6cb..3ec2ee912c3 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 77cce7cc768..93e44737a05 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 7778d1dbc8c..7286808f503 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 00000000000..9fee6b7f8c4 --- /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); + }); +}); From 3a638af6f67e93cc6a029e4d96033ad9dabf555b Mon Sep 17 00:00:00 2001 From: originRing <48777107+OriginRing@users.noreply.github.com> Date: Mon, 7 Nov 2022 10:29:15 +0800 Subject: [PATCH 4/8] feat(module:cron-expression): add cron-expression component (#7677) * feat(module:cron-expression): add cron-expression component * feat(module:cron-expression): add cron-expression component i18n * feat(module:cron-expression): modify cron-expression i18.interface * feat(module:cron-expression): detail modification * feat(module:cron-expression): cron-expression support spring * feat(module:cron-expression): adjust the layout --- components/components.less | 1 + .../cron-expression-input.component.ts | 49 ++++ .../cron-expression-label.component.ts | 44 +++ .../cron-expression.component.ts | 267 ++++++++++++++++++ .../cron-expression/cron-expression.module.ts | 36 +++ components/cron-expression/demo/basic.md | 14 + components/cron-expression/demo/basic.ts | 7 + components/cron-expression/demo/collapse.md | 14 + components/cron-expression/demo/collapse.ts | 7 + components/cron-expression/demo/module | 8 + components/cron-expression/demo/shortcuts.md | 14 + components/cron-expression/demo/shortcuts.ts | 52 ++++ components/cron-expression/demo/size.md | 14 + components/cron-expression/demo/size.ts | 20 ++ components/cron-expression/demo/type.md | 14 + components/cron-expression/demo/type.ts | 19 ++ components/cron-expression/demo/use.md | 14 + components/cron-expression/demo/use.ts | 50 ++++ components/cron-expression/doc/index.en-US.md | 40 +++ components/cron-expression/doc/index.zh-CN.md | 40 +++ components/cron-expression/index.ts | 6 + components/cron-expression/ng-package.json | 6 + components/cron-expression/public-api.ts | 8 + components/cron-expression/style/entry.less | 1 + components/cron-expression/style/index.less | 119 ++++++++ components/cron-expression/typings.ts | 24 ++ components/i18n/languages/en_US.ts | 21 ++ components/i18n/languages/zh_CN.ts | 21 ++ components/i18n/nz-i18n.interface.ts | 23 ++ package.json | 1 + 30 files changed, 954 insertions(+) create mode 100644 components/cron-expression/cron-expression-input.component.ts create mode 100644 components/cron-expression/cron-expression-label.component.ts create mode 100644 components/cron-expression/cron-expression.component.ts create mode 100644 components/cron-expression/cron-expression.module.ts create mode 100644 components/cron-expression/demo/basic.md create mode 100644 components/cron-expression/demo/basic.ts create mode 100644 components/cron-expression/demo/collapse.md create mode 100644 components/cron-expression/demo/collapse.ts create mode 100644 components/cron-expression/demo/module create mode 100644 components/cron-expression/demo/shortcuts.md create mode 100644 components/cron-expression/demo/shortcuts.ts create mode 100644 components/cron-expression/demo/size.md create mode 100644 components/cron-expression/demo/size.ts create mode 100644 components/cron-expression/demo/type.md create mode 100644 components/cron-expression/demo/type.ts create mode 100644 components/cron-expression/demo/use.md create mode 100644 components/cron-expression/demo/use.ts create mode 100644 components/cron-expression/doc/index.en-US.md create mode 100644 components/cron-expression/doc/index.zh-CN.md create mode 100644 components/cron-expression/index.ts create mode 100644 components/cron-expression/ng-package.json create mode 100644 components/cron-expression/public-api.ts create mode 100644 components/cron-expression/style/entry.less create mode 100644 components/cron-expression/style/index.less create mode 100644 components/cron-expression/typings.ts diff --git a/components/components.less b/components/components.less index dfc24172a46..3f4772303da 100644 --- a/components/components.less +++ b/components/components.less @@ -62,3 +62,4 @@ @import './result/style/entry.less'; @import './space/style/entry.less'; @import './image/style/entry.less'; +@import './cron-expression/style/entry.less'; diff --git a/components/cron-expression/cron-expression-input.component.ts b/components/cron-expression/cron-expression-input.component.ts new file mode 100644 index 00000000000..0d9c3c9271a --- /dev/null +++ b/components/cron-expression/cron-expression-input.component.ts @@ -0,0 +1,49 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +import { Component, ViewEncapsulation, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core'; + +import { CronChangeType, TimeType } from './typings'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + selector: 'nz-cron-expression-input', + exportAs: 'nzCronExpression', + template: ` +
+ +
+ ` +}) +export class NzCronExpressionInputComponent { + @Input() value: string = '0'; + @Input() label: TimeType = 'second'; + @Output() readonly focusEffect = new EventEmitter(); + @Output() readonly blurEffect = new EventEmitter(); + @Output() readonly getValue = new EventEmitter(); + + constructor() {} + + focusInputEffect(event: FocusEvent): void { + this.focusEffect.emit(this.label); + (event.target as HTMLInputElement).select(); + } + + blurInputEffect(): void { + this.blurEffect.emit(); + } + + setValue(): void { + this.getValue.emit({ label: this.label, value: this.value }); + } +} diff --git a/components/cron-expression/cron-expression-label.component.ts b/components/cron-expression/cron-expression-label.component.ts new file mode 100644 index 00000000000..be98dfd1e87 --- /dev/null +++ b/components/cron-expression/cron-expression-label.component.ts @@ -0,0 +1,44 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +import { Component, ViewEncapsulation, ChangeDetectionStrategy, Input, OnInit } from '@angular/core'; + +import { NzCronExpressionLabelI18n } from 'ng-zorro-antd/i18n'; + +import { TimeType, TimeTypeError } from './typings'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + selector: 'nz-cron-expression-label', + exportAs: 'nzCronExpression', + template: ` +
+ +
+ +
+
+ ` +}) +export class NzCronExpressionLabelComponent implements OnInit { + @Input() type: TimeType = 'second'; + @Input() valid: boolean = true; + @Input() locale!: NzCronExpressionLabelI18n; + @Input() labelFocus: string | null = null; + labelError: TimeTypeError = 'secondError'; + + constructor() {} + + ngOnInit(): void { + this.labelError = `${this.type}Error`; + } +} diff --git a/components/cron-expression/cron-expression.component.ts b/components/cron-expression/cron-expression.component.ts new file mode 100644 index 00000000000..420776b0688 --- /dev/null +++ b/components/cron-expression/cron-expression.component.ts @@ -0,0 +1,267 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + forwardRef, + Input, + OnDestroy, + OnInit, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { + AsyncValidator, + ControlValueAccessor, + FormControl, + NG_ASYNC_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + ValidationErrors, + Validators +} from '@angular/forms'; +import { Observable, of, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { CronExpression, parseExpression } from 'cron-parser'; + +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { InputBoolean } from 'ng-zorro-antd/core/util'; +import { NzCronExpressionI18nInterface, NzI18nService } from 'ng-zorro-antd/i18n'; + +import { CronChangeType, CronType, NzCronExpressionSize, TimeType } from './typings'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + selector: 'nz-cron-expression', + exportAs: 'nzCronExpression', + template: ` +
+
+
+ + + +
+
+ + + +
+ + + +
    +
  • + {{ dateItem | date: 'YYYY-MM-dd HH:mm:ss' }} +
  • +
  • ···
  • +
+
+ {{ locale.cronError }} +
+
+
+
+ +
+ + + {{ dateTime | date: 'YYYY-MM-dd HH:mm:ss' }} + + {{ locale.cronError }} + +
+ `, + providers: [ + { + provide: NG_ASYNC_VALIDATORS, + useExisting: forwardRef(() => NzCronExpressionComponent), + multi: true + }, + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => NzCronExpressionComponent), + multi: true + } + ] +}) +export class NzCronExpressionComponent implements OnInit, ControlValueAccessor, AsyncValidator, OnDestroy { + @Input() nzSize: NzCronExpressionSize = 'default'; + @Input() nzType: 'linux' | 'spring' = 'linux'; + @Input() @InputBoolean() nzCollapseDisable: boolean = false; + @Input() nzExtra?: TemplateRef | null = null; + + locale!: NzCronExpressionI18nInterface; + focus: boolean = false; + labelFocus: TimeType | null = null; + validLabel: string | null = null; + labels: TimeType[] = []; + interval!: CronExpression; + nextTimeList: Date[] = []; + dateTime: Date = new Date(); + private destroy$ = new Subject(); + + validateForm!: UntypedFormGroup; + + onChange: NzSafeAny = () => {}; + onTouch: () => void = () => null; + + convertFormat(value: string): void { + const values = value.split(' '); + const valueObject: CronType = {}; + this.labels.map((a, b) => { + valueObject[a] = values[b]; + }); + this.validateForm.patchValue(valueObject); + } + + writeValue(value: string | null): void { + if (value) { + this.convertFormat(value); + } + } + + registerOnChange(fn: NzSafeAny): void { + this.onChange = fn; + } + + registerOnTouched(fn: NzSafeAny): void { + this.onTouch = fn; + } + + validate(): Observable { + if (this.validateForm.valid) { + return of(null); + } else { + return of({ error: true }); + } + } + + constructor(private formBuilder: UntypedFormBuilder, private cdr: ChangeDetectorRef, private i18n: NzI18nService) {} + + ngOnInit(): void { + if (this.nzType === 'spring') { + this.labels = ['second', 'minute', 'hour', 'day', 'month', 'week']; + this.validateForm = this.formBuilder.group({ + second: ['0', Validators.required, this.checkValid], + minute: ['*', Validators.required, this.checkValid], + hour: ['*', Validators.required, this.checkValid], + day: ['*', Validators.required, this.checkValid], + month: ['*', Validators.required, this.checkValid], + week: ['*', Validators.required, this.checkValid] + }); + } else { + this.labels = ['minute', 'hour', 'day', 'month', 'week']; + this.validateForm = this.formBuilder.group({ + minute: ['*', Validators.required, this.checkValid], + hour: ['*', Validators.required, this.checkValid], + day: ['*', Validators.required, this.checkValid], + month: ['*', Validators.required, this.checkValid], + week: ['*', Validators.required, this.checkValid] + }); + } + this.i18n.localeChange.pipe(takeUntil(this.destroy$)).subscribe(() => { + this.locale = this.i18n.getLocaleData('CronExpression'); + this.cdr.markForCheck(); + }); + + this.previewDate(this.validateForm.value); + + this.validateForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => { + this.onChange(Object.values(value).join(' ')); + this.previewDate(value); + this.cdr.markForCheck(); + }); + } + + previewDate(value: CronType): void { + try { + this.interval = parseExpression(Object.values(value).join(' ')); + this.dateTime = this.interval.next().toDate(); + this.nextTimeList = [ + this.interval.next().toDate(), + this.interval.next().toDate(), + this.interval.next().toDate(), + this.interval.next().toDate(), + this.interval.next().toDate() + ]; + } catch (err: NzSafeAny) { + return; + } + } + + loadMorePreview(): void { + this.nextTimeList = [ + ...this.nextTimeList, + this.interval.next().toDate(), + this.interval.next().toDate(), + this.interval.next().toDate(), + this.interval.next().toDate(), + this.interval.next().toDate() + ]; + this.cdr.markForCheck(); + } + + focusEffect(value: TimeType): void { + this.focus = true; + this.labelFocus = value; + this.cdr.markForCheck(); + } + + blurEffect(): void { + this.focus = false; + this.labelFocus = null; + this.cdr.markForCheck(); + } + + getValue(item: CronChangeType): void { + this.validLabel = item.label; + this.validateForm.controls[item.label].patchValue(item.value); + this.cdr.markForCheck(); + } + + checkValid = (control: FormControl): Observable => { + if (control.value) { + try { + const cron: string[] = []; + this.labels.forEach(label => { + label === this.validLabel ? cron.push(control.value) : cron.push('*'); + }); + parseExpression(cron.join(' ')); + } catch (err: unknown) { + return of({ error: true }); + } + } + return of(null); + }; + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/components/cron-expression/cron-expression.module.ts b/components/cron-expression/cron-expression.module.ts new file mode 100644 index 00000000000..99a837ef0dc --- /dev/null +++ b/components/cron-expression/cron-expression.module.ts @@ -0,0 +1,36 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +import { ScrollingModule } from '@angular/cdk/scrolling'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; + +import { NzCollapseModule } from 'ng-zorro-antd/collapse'; +import { NzFormModule } from 'ng-zorro-antd/form'; +import { NzIconModule } from 'ng-zorro-antd/icon'; +import { NzInputModule } from 'ng-zorro-antd/input'; +import { NzToolTipModule } from 'ng-zorro-antd/tooltip'; + +import { NzCronExpressionInputComponent } from './cron-expression-input.component'; +import { NzCronExpressionLabelComponent } from './cron-expression-label.component'; +import { NzCronExpressionComponent } from './cron-expression.component'; + +@NgModule({ + declarations: [NzCronExpressionComponent, NzCronExpressionLabelComponent, NzCronExpressionInputComponent], + imports: [ + CommonModule, + ReactiveFormsModule, + NzToolTipModule, + NzCollapseModule, + NzFormModule, + NzInputModule, + NzIconModule, + FormsModule, + ScrollingModule + ], + exports: [NzCronExpressionComponent] +}) +export class NzCronExpressionModule {} diff --git a/components/cron-expression/demo/basic.md b/components/cron-expression/demo/basic.md new file mode 100644 index 00000000000..e2d2a9c3711 --- /dev/null +++ b/components/cron-expression/demo/basic.md @@ -0,0 +1,14 @@ +--- +order: 0 +title: + zh-CN: 基本 + en-US: Basic +--- + +## zh-CN + +最简单的用法。 + +## en-US + +The simplest usage. diff --git a/components/cron-expression/demo/basic.ts b/components/cron-expression/demo/basic.ts new file mode 100644 index 00000000000..b9390c88ec3 --- /dev/null +++ b/components/cron-expression/demo/basic.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-cron-expression-basic', + template: ` ` +}) +export class NzDemoCronExpressionBasicComponent {} diff --git a/components/cron-expression/demo/collapse.md b/components/cron-expression/demo/collapse.md new file mode 100644 index 00000000000..49a251e74b1 --- /dev/null +++ b/components/cron-expression/demo/collapse.md @@ -0,0 +1,14 @@ +--- +order: 3 +title: + zh-CN: 隐藏折叠面板 + en-US: Hide Collapse +--- + +## zh-CN + +`[nzCollapseDisable]="true"` + +## en-US + +`[nzCollapseDisable]="true"` \ No newline at end of file diff --git a/components/cron-expression/demo/collapse.ts b/components/cron-expression/demo/collapse.ts new file mode 100644 index 00000000000..212570abfee --- /dev/null +++ b/components/cron-expression/demo/collapse.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-cron-expression-collapse', + template: ` ` +}) +export class NzDemoCronExpressionCollapseComponent {} diff --git a/components/cron-expression/demo/module b/components/cron-expression/demo/module new file mode 100644 index 00000000000..44f892d2b97 --- /dev/null +++ b/components/cron-expression/demo/module @@ -0,0 +1,8 @@ +import { NzCronExpressionModule } from 'ng-zorro-antd/cron-expression'; +import { NzFormModule } from 'ng-zorro-antd/form'; +import { NzInputModule } from 'ng-zorro-antd/input'; +import { NzButtonModule } from 'ng-zorro-antd/button'; +import { NzDropDownModule } from 'ng-zorro-antd/dropdown'; +import { NzIconModule } from 'ng-zorro-antd/icon'; + +export const moduleList = [NzCronExpressionModule, NzFormModule, NzInputModule, NzButtonModule, NzDropDownModule, NzIconModule]; diff --git a/components/cron-expression/demo/shortcuts.md b/components/cron-expression/demo/shortcuts.md new file mode 100644 index 00000000000..414a555261a --- /dev/null +++ b/components/cron-expression/demo/shortcuts.md @@ -0,0 +1,14 @@ +--- +order: 4 +title: + zh-CN: 额外节点 + en-US: Extra Node +--- + +## zh-CN + +你可以通过 nzExtra 来指定右侧的内容。 + +## en-US + +You can use nzExtra to specify the content on the right. \ No newline at end of file diff --git a/components/cron-expression/demo/shortcuts.ts b/components/cron-expression/demo/shortcuts.ts new file mode 100644 index 00000000000..c61cbd51bb2 --- /dev/null +++ b/components/cron-expression/demo/shortcuts.ts @@ -0,0 +1,52 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-cron-expression-shortcuts', + template: ` + + + + +
    +
  • {{ + item.label + }}
  • +
+
+
+

cron: {{ cron }}

+ ` +}) +export class NzDemoCronExpressionShortcutsComponent { + value: string = '1 1 * * *'; + cron: string = ''; + options = [ + { + label: 'Every hour', + value: '0 0-23/1 * * *' + }, + { + label: 'Every day at eight', + value: '0 8 * * *' + }, + { + label: 'Every Friday', + value: '0 0 * * 5' + } + ]; + + setValue(value: string): void { + this.value = value; + } + + getValue(value: string): void { + this.cron = value; + } +} diff --git a/components/cron-expression/demo/size.md b/components/cron-expression/demo/size.md new file mode 100644 index 00000000000..548524712b4 --- /dev/null +++ b/components/cron-expression/demo/size.md @@ -0,0 +1,14 @@ +--- +order: 1 +title: + zh-CN: 三种大小 + en-US: Three sizes of Input +--- + +## zh-CN + +我们为 `nz-cron-expression` 输入框定义了三种尺寸(大、默认、小),高度分别为 `40px`、`32px` 和 `24px`。 + +## en-US + +There are three sizes of an CronExpression box: `large` (40px)、`default` (32px) and `small` (24px). diff --git a/components/cron-expression/demo/size.ts b/components/cron-expression/demo/size.ts new file mode 100644 index 00000000000..c7ddd12699c --- /dev/null +++ b/components/cron-expression/demo/size.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-cron-expression-size', + template: ` +
+ + + +
+ `, + styles: [ + ` + .example-cron-expression nz-cron-expression { + margin: 0 8px 8px 0; + } + ` + ] +}) +export class NzDemoCronExpressionSizeComponent {} diff --git a/components/cron-expression/demo/type.md b/components/cron-expression/demo/type.md new file mode 100644 index 00000000000..e642cf8b8b6 --- /dev/null +++ b/components/cron-expression/demo/type.md @@ -0,0 +1,14 @@ +--- +order: 2 +title: + zh-CN: 两种规则类型 + en-US: Two rule types +--- + +## zh-CN + +我们为 `nz-cron-expression` 输入框定义了两种规则类型(五段式, 六段式),分别为: `nzType="linux"`、`nzType="spring"`。 + +## en-US + +There are two rule types of an CronExpression box: `nzType="linux"` (five-segment) and`nzType="spring"` (six-segment). diff --git a/components/cron-expression/demo/type.ts b/components/cron-expression/demo/type.ts new file mode 100644 index 00000000000..40c5b858bcb --- /dev/null +++ b/components/cron-expression/demo/type.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-cron-expression-type', + template: ` +
+ + +
+ `, + styles: [ + ` + .example-cron-expression nz-cron-expression { + margin: 0 8px 8px 0; + } + ` + ] +}) +export class NzDemoCronExpressionTypeComponent {} diff --git a/components/cron-expression/demo/use.md b/components/cron-expression/demo/use.md new file mode 100644 index 00000000000..4fbb2a4fcd8 --- /dev/null +++ b/components/cron-expression/demo/use.md @@ -0,0 +1,14 @@ +--- +order: 5 +title: + zh-CN: 结合表单使用 + en-US: Basic +--- + +## zh-CN + +`` + +## en-US + +`` diff --git a/components/cron-expression/demo/use.ts b/components/cron-expression/demo/use.ts new file mode 100644 index 00000000000..32bd0d517c1 --- /dev/null +++ b/components/cron-expression/demo/use.ts @@ -0,0 +1,50 @@ +import { Component, OnInit } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; + +@Component({ + selector: 'nz-demo-cron-expression-use', + template: ` +
+ + name + + + + + + nz-cron-linux + + + + + + nz-cron-spring + + + + + + + + + +
+ ` +}) +export class NzDemoCronExpressionUseComponent implements OnInit { + validateForm!: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) {} + + ngOnInit(): void { + this.validateForm = this.fb.group({ + userName: ['cron-expression', [Validators.required]], + cronLinux: ['* 1 * * *', [Validators.required]], + cronSpring: ['0 * 1 * * *', [Validators.required]] + }); + } + + submitForm(): void { + console.log(this.validateForm.value); + } +} diff --git a/components/cron-expression/doc/index.en-US.md b/components/cron-expression/doc/index.en-US.md new file mode 100644 index 00000000000..5d3a9b7514d --- /dev/null +++ b/components/cron-expression/doc/index.en-US.md @@ -0,0 +1,40 @@ +--- +category: Components +subtitle: cron form +type: Data Entry +title: Cron Expression +cols: 1 +experimental: true +--- + +
+

NG-ZORRO experiments are features that are released but not yet considered stable or production ready

+

Developers and users can opt-in into these features before they are fully released. But breaking changes may occur with any release.

+
+ +## When To Use + +When you want to use cron in Angular. + +### Import Module + +```ts +import { NzCronExpressionModule } from 'ng-zorro-antd/cron-expression'; +``` + +## API + +Install `cron-parser` in your project first: + +```sh +npm install cron-parser +``` + +### nz-cron-expression + +| Parameter | Description | Type | Default | +|-------------|--------------------------------------------------|-------------|---------| +| `[nzType]` | Cron rule type | `'linux'|'spring'` | `linux` | +| `[nzSize]` | The size of the input box. | `'large'|'small'|'default'` | `default` | +| `[nzCollapseDisable]` | Hide collapse | `boolean` | `false` | +| `[nzExtra]` | Render the content on the right | `TemplateRef` | - | \ No newline at end of file diff --git a/components/cron-expression/doc/index.zh-CN.md b/components/cron-expression/doc/index.zh-CN.md new file mode 100644 index 00000000000..b17ea22a71c --- /dev/null +++ b/components/cron-expression/doc/index.zh-CN.md @@ -0,0 +1,40 @@ +--- +category: Components +subtitle: cron 表单 +type: 数据录入 +title: Cron Expression +cols: 1 +experimental: true +--- + +
+

NG-ZORRO 实验性功能是指已发布但不稳定或者还未准备好用于生产环境的功能。

+

开发者或用户可以选择在正式发布前使用这些功能,但是每次发布版本时都可能存在 breaking changes

+
+ +## 何时使用 + +需要在表单中使用 cron 格式验证时使用。 + +### 引入模块 + +```ts +import { NzCronExpressionModule } from 'ng-zorro-antd/cron-expression'; +``` + +## API + +别忘记先安装 cron-parser: + +```sh +npm install cron-parser +``` + +### nz-cron-expression + +| 参数 | 说明 | 类型 | 默认值 | +|----------------|----------------|-----------------------------|----------| +| `[nzType]` | cron 规则类型 | `'linux'|'spring'` | `linux` | +| `[nzSize]` | 设置输入框大小 | `'large'|'small'|'default'` | `default` | +| `[nzCollapseDisable]` | 隐藏折叠面板 | `boolean` | `false` | +| `[nzExtra]` | 自定义渲染右侧的内容 | `TemplateRef` | - | \ No newline at end of file diff --git a/components/cron-expression/index.ts b/components/cron-expression/index.ts new file mode 100644 index 00000000000..97717c1c837 --- /dev/null +++ b/components/cron-expression/index.ts @@ -0,0 +1,6 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +export * from './public-api'; diff --git a/components/cron-expression/ng-package.json b/components/cron-expression/ng-package.json new file mode 100644 index 00000000000..6f8de7f4f1e --- /dev/null +++ b/components/cron-expression/ng-package.json @@ -0,0 +1,6 @@ +{ + "lib": { + "entryFile": "public-api.ts" + }, + "allowedNonPeerDependencies": ["cron-parser"] +} diff --git a/components/cron-expression/public-api.ts b/components/cron-expression/public-api.ts new file mode 100644 index 00000000000..19fc8274dd9 --- /dev/null +++ b/components/cron-expression/public-api.ts @@ -0,0 +1,8 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +export * from './typings'; +export * from './cron-expression.component'; +export * from './cron-expression.module'; diff --git a/components/cron-expression/style/entry.less b/components/cron-expression/style/entry.less new file mode 100644 index 00000000000..84d0bb2dbd1 --- /dev/null +++ b/components/cron-expression/style/entry.less @@ -0,0 +1 @@ +@import './index.less'; \ No newline at end of file diff --git a/components/cron-expression/style/index.less b/components/cron-expression/style/index.less new file mode 100644 index 00000000000..b80737d5b08 --- /dev/null +++ b/components/cron-expression/style/index.less @@ -0,0 +1,119 @@ +@import '../../style/themes/index'; +@import '../../style/mixins/index'; + +@cron-expression-prefix-cls: ~'@{ant-prefix}-cron-expression'; + +.@{cron-expression-prefix-cls} { + display: flex; + flex-wrap: nowrap; + + &-content{ + width: 100%; + + .@{cron-expression-prefix-cls}-input-group-error { + border-color: @error-color; + box-shadow: none; + + &:hover { + border-color: @error-color; + box-shadow: none; + } + } + + .@{cron-expression-prefix-cls}-input-group-error-focus { + box-shadow: 0 0 0 2px @error-color-outline; + &:hover { + box-shadow: 0 0 0 2px @error-color-outline; + } + } + } + + nz-cron-expression-input{ + width: 20%; + } + + &-input-group { + display: flex; + flex-wrap: nowrap; + align-items: center; + border: @border-width-base solid @border-color-base; + padding: @padding-xss @padding-sm; + border-radius: @border-radius-base; + + &:hover { + border-color: @primary-color; + } + + input { + border: none !important; + box-shadow: none !important; + width: 100%; + outline: none; + padding: 0; + border-radius: 0; + } + } + + &-input-group-lg { + padding: 6.5px @padding-sm; + font-size: @font-size-lg; + } + + &-input-group-sm { + padding: 0 @padding-sm; + } + + &-input-group-focus { + border-color: @primary-color; + box-shadow: 0 0 0 2px @primary-color-outline; + border-right-width: @border-width-base; + outline: 0; + } + + nz-cron-expression-label { + width: 20%; + } + + &-label-group { + display: flex; + width: 100%; + flex-wrap: nowrap; + justify-content: space-around; + padding: 0 @padding-sm; + } + + &-label-foucs { + color: @primary-color; + } + + &-map { + margin-left: @margin-sm; + } + + &-preview-date { + overflow-y: scroll; + height: 132px; + } + + &-error { + color: @error-color; + } + + &-hint { + p { + display: flex; + } + span { + display: inline-block; + min-width: 40px; + } + } +} + +.ant-collapse > .ant-collapse-item > .ant-collapse-header { + padding: @padding-sm; +} + +.ant-collapse-content > .ant-collapse-content-box { + padding: @padding-md @padding-lg+@padding-sm; +} \ No newline at end of file diff --git a/components/cron-expression/typings.ts b/components/cron-expression/typings.ts new file mode 100644 index 00000000000..2a3e906781b --- /dev/null +++ b/components/cron-expression/typings.ts @@ -0,0 +1,24 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +export type TimeType = 'second' | 'minute' | 'hour' | 'day' | 'month' | 'week'; + +export type TimeTypeError = 'secondError' | 'minuteError' | 'hourError' | 'dayError' | 'monthError' | 'weekError'; + +export interface CronType { + second?: string; + minute?: string; + hour?: string; + day?: string; + month?: string; + week?: string; +} + +export interface CronChangeType { + label: string; + value: string; +} + +export type NzCronExpressionSize = 'large' | 'default' | 'small'; diff --git a/components/i18n/languages/en_US.ts b/components/i18n/languages/en_US.ts index 57ae72bfe91..fc23dcc0973 100644 --- a/components/i18n/languages/en_US.ts +++ b/components/i18n/languages/en_US.ts @@ -177,5 +177,26 @@ export default { }, Image: { preview: 'Preview' + }, + CronExpression: { + cronError: 'Invalid cron expression', + second: 'second', + minute: 'minute', + hour: 'hour', + day: 'day', + month: 'month', + week: 'week', + secondError: + '

*Any value

,Separator between multiple values

-Connector for interval values

/Equally distributed

0-59Allowable range

', + minuteError: + '

*Any value

,Separator between multiple values

-Connector for interval values

/Equally distributed

0-59Allowable range

', + hourError: + '

*Any value

,Separator between multiple values

-Connector for interval values

/Equally distributed

0-23Allowable range

', + dayError: + '

*Any value

,Separator between multiple values

-Connector for interval values

/Equally distributed

1-31Allowable range

', + monthError: + '

*Any value

,Separator between multiple values

-Connector for interval values

/Equally distributed

1-12Allowable range

', + weekError: + '

*Any value

,Separator between multiple values

-Connector for interval values

/Equally distributed

? Not specify

0-7Allowable range (0 represents Sunday, 1-7 are Monday to Sunday)

' } }; diff --git a/components/i18n/languages/zh_CN.ts b/components/i18n/languages/zh_CN.ts index 4008de11cac..45fa17658d7 100755 --- a/components/i18n/languages/zh_CN.ts +++ b/components/i18n/languages/zh_CN.ts @@ -173,5 +173,26 @@ export default { }, Image: { preview: '预览' + }, + CronExpression: { + cronError: 'cron 表达式不合法', + second: '秒', + minute: '分钟', + hour: '小时', + day: '日', + month: '月', + week: '周', + secondError: + '

*任意值

,多个值之间的分隔符

-区间值的连接符

/平均分配

0-59允许范围

', + minuteError: + '

*任意值

,多个值之间的分隔符

-区间值的连接符

/平均分配

0-59允许范围

', + hourError: + '

* 任意值

, 多个值之间的分隔符

- 区间值的连接符

/ 平均分配

0-23 允许范围

', + dayError: + '

* 任意值

, 多个值之间的分隔符

- 区间值的连接符

/ 平均分配

1-31 允许范围

', + monthError: + '

* 任意值

, 多个值之间的分隔符

- 区间值的连接符

/ 平均分配

1-12 允许范围

', + weekError: + '

* 任意值

, 多个值之间的分隔符

- 区间值的连接符

/ 平均分配

? 不指定

0-7 允许范围(0代表周日,1-7依次为周一到周日)

' } }; diff --git a/components/i18n/nz-i18n.interface.ts b/components/i18n/nz-i18n.interface.ts index a427717c164..a4e000a9a8a 100644 --- a/components/i18n/nz-i18n.interface.ts +++ b/components/i18n/nz-i18n.interface.ts @@ -131,6 +131,28 @@ export interface NzTextI18nInterface { expand: string; } +export interface NzCronExpressionLabelI18n { + second?: string; + minute?: string; + hour?: string; + day?: string; + month?: string; + week?: string; + // innerHTML + secondError?: string; + minuteError?: string; + hourError?: string; + dayError?: string; + monthError?: string; + weekError?: string; +} + +export interface NzCronExpressionCronErrorI18n { + cronError?: string; +} + +export type NzCronExpressionI18nInterface = NzCronExpressionCronErrorI18n & NzCronExpressionLabelI18n; + export interface NzI18nInterface { locale: string; Pagination: NzPaginationI18nInterface; @@ -145,6 +167,7 @@ export interface NzI18nInterface { Upload: NzUploadI18nInterface; Empty: NzEmptyI18nInterface; Text?: NzTextI18nInterface; + CronExpression?: NzCronExpressionI18nInterface; } export type DateLocale = Locale; diff --git a/package.json b/package.json index 8cbd408642c..a4e26842e53 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "codelyzer": "^6.0.1", "codesandbox": "^2.2.1", "conventional-changelog-cli": "^2.1.1", + "cron-parser": "^4.6.0", "d3": "^6.3.1", "dagre": "^0.8.5", "dagre-compound": "0.0.8", From 726ded31a8a8d9ad46a5095ece21e31c321cd10c Mon Sep 17 00:00:00 2001 From: originRing <48777107+OriginRing@users.noreply.github.com> Date: Tue, 15 Nov 2022 16:01:53 +0800 Subject: [PATCH 5/8] fix(module:cron-expression): clear ul & li default style (#7715) --- components/cron-expression/cron-expression.component.ts | 2 +- components/cron-expression/cron-expression.module.ts | 4 +--- components/cron-expression/style/index.less | 7 +++++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/components/cron-expression/cron-expression.component.ts b/components/cron-expression/cron-expression.component.ts index 420776b0688..78c59bfacb9 100644 --- a/components/cron-expression/cron-expression.component.ts +++ b/components/cron-expression/cron-expression.component.ts @@ -76,7 +76,7 @@ import { CronChangeType, CronType, NzCronExpressionSize, TimeType } from './typi
    -
  • +
  • {{ dateItem | date: 'YYYY-MM-dd HH:mm:ss' }}
  • ···
  • diff --git a/components/cron-expression/cron-expression.module.ts b/components/cron-expression/cron-expression.module.ts index 99a837ef0dc..7904478b960 100644 --- a/components/cron-expression/cron-expression.module.ts +++ b/components/cron-expression/cron-expression.module.ts @@ -3,7 +3,6 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { ScrollingModule } from '@angular/cdk/scrolling'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -28,8 +27,7 @@ import { NzCronExpressionComponent } from './cron-expression.component'; NzFormModule, NzInputModule, NzIconModule, - FormsModule, - ScrollingModule + FormsModule ], exports: [NzCronExpressionComponent] }) diff --git a/components/cron-expression/style/index.less b/components/cron-expression/style/index.less index b80737d5b08..620b511d639 100644 --- a/components/cron-expression/style/index.less +++ b/components/cron-expression/style/index.less @@ -93,6 +93,13 @@ &-preview-date { overflow-y: scroll; height: 132px; + margin: 0; + padding: 0; + li { + list-style: none; + margin: 0; + padding: 0; + } } &-error { From 754ded61fe41d523c2bf216a7ea49cc2a5a6fa61 Mon Sep 17 00:00:00 2001 From: Artur Androsovych Date: Tue, 15 Nov 2022 08:02:11 +0000 Subject: [PATCH 6/8] fix(module:icon): re-enter Angular zone after icons have been loaded (#7719) --- components/icon/icon.directive.ts | 33 ++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/components/icon/icon.directive.ts b/components/icon/icon.directive.ts index f75e7eccf3d..6c97a8556bb 100644 --- a/components/icon/icon.directive.ts +++ b/components/icon/icon.directive.ts @@ -136,23 +136,32 @@ export class NzIconDirective extends IconDirective implements OnInit, OnChanges, private changeIcon2(): void { this.setClassName(); - // We don't need to re-enter the Angular zone for adding classes or attributes through the renderer. + // The Angular zone is left deliberately before the SVG is set + // since `_changeIcon` spawns asynchronous tasks as promise and + // HTTP calls. This is used to reduce the number of change detections + // while the icon is being loaded dynamically. this.ngZone.runOutsideAngular(() => { from(this._changeIcon()) .pipe(takeUntil(this.destroy$)) .subscribe({ next: svgOrRemove => { - // The _changeIcon method would call Renderer to remove the element of the old icon, - // which would call `markElementAsRemoved` eventually, - // so we should call `detectChanges` to tell Angular remove the DOM node. - // #7186 - this.changeDetectorRef.detectChanges(); - - if (svgOrRemove) { - this.setSVGData(svgOrRemove); - this.handleSpin(svgOrRemove); - this.handleRotate(svgOrRemove); - } + // Get back into the Angular zone after completing all the tasks. + // Since we manually run change detection locally, we have to re-enter + // the zone because the change detection might also be run on other local + // components, leading them to handle template functions outside of the Angular zone. + this.ngZone.run(() => { + // The _changeIcon method would call Renderer to remove the element of the old icon, + // which would call `markElementAsRemoved` eventually, + // so we should call `detectChanges` to tell Angular remove the DOM node. + // #7186 + this.changeDetectorRef.detectChanges(); + + if (svgOrRemove) { + this.setSVGData(svgOrRemove); + this.handleSpin(svgOrRemove); + this.handleRotate(svgOrRemove); + } + }); }, error: warn }); From f6a804408e21f0ae7dc7385da202acd54e76cdd7 Mon Sep 17 00:00:00 2001 From: HyperLifelll9 Date: Tue, 15 Nov 2022 16:02:52 +0800 Subject: [PATCH 7/8] feat(module:popconfirm): make nzOkDanger coerce to boolean (#7720) --- components/popconfirm/popconfirm.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/popconfirm/popconfirm.ts b/components/popconfirm/popconfirm.ts index d20b828939c..722f0edd603 100644 --- a/components/popconfirm/popconfirm.ts +++ b/components/popconfirm/popconfirm.ts @@ -51,6 +51,7 @@ const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'popconfirm'; }) export class NzPopconfirmDirective extends NzTooltipBaseDirective { readonly _nzModuleName: NzConfigKey = NZ_CONFIG_MODULE_NAME; + static ngAcceptInputType_nzOkDanger: BooleanInput; static ngAcceptInputType_nzCondition: BooleanInput; static ngAcceptInputType_nzPopconfirmShowArrow: BooleanInput; static ngAcceptInputType_nzPopconfirmArrowPointAtCenter: BooleanInput; @@ -68,7 +69,7 @@ export class NzPopconfirmDirective extends NzTooltipBaseDirective { @Input('nzPopconfirmVisible') override visible?: boolean; @Input() nzOkText?: string; @Input() nzOkType?: string; - @Input() nzOkDanger?: boolean; + @Input() @InputBoolean() nzOkDanger?: boolean; @Input() nzCancelText?: string; @Input() nzBeforeConfirm?: () => Observable | Promise | boolean; @Input() nzIcon?: string | TemplateRef; From 616f59ffe80b2f5a6d6e41787eec29de240901d4 Mon Sep 17 00:00:00 2001 From: chenc Date: Thu, 17 Nov 2022 10:06:06 +0800 Subject: [PATCH 8/8] fix(module:image): preview the local upload image error (#7615) --- components/image/image-preview.component.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/components/image/image-preview.component.ts b/components/image/image-preview.component.ts index 8b4dc203e8f..0e085e5c5c5 100644 --- a/components/image/image-preview.component.ts +++ b/components/image/image-preview.component.ts @@ -16,6 +16,7 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { fromEvent } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -75,7 +76,7 @@ const initialPosition = { class="ant-image-preview-img" #imgRef *ngIf="index === imageIndex" - [attr.src]="image.src" + [attr.src]="sanitizerResourceUrl(image.src)" [attr.srcset]="image.srcset" [attr.alt]="image.alt" [style.width]="image.width" @@ -197,7 +198,8 @@ export class NzImagePreviewComponent implements OnInit { public nzConfigService: NzConfigService, public config: NzImagePreviewOptions, private overlayRef: OverlayRef, - private destroy$: NzDestroyService + private destroy$: NzDestroyService, + private sanitizer: DomSanitizer ) { this.zoom = this.config.nzZoom ?? 1; this.rotate = this.config.nzRotate ?? 0; @@ -347,6 +349,10 @@ export class NzImagePreviewComponent implements OnInit { } } + sanitizerResourceUrl(url: string): SafeResourceUrl { + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } + private updatePreviewImageTransform(): void { this.previewImageTransform = `scale3d(${this.zoom}, ${this.zoom}, 1) rotate(${this.rotate}deg)`; }