diff --git a/components/core/util/keycodes.ts b/components/core/util/keycodes.ts deleted file mode 100644 index 5b8691c3ee0..00000000000 --- a/components/core/util/keycodes.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@angular/cdk/keycodes'; diff --git a/components/core/util/public-api.ts b/components/core/util/public-api.ts index 7804f240e0e..969fdecdf90 100644 --- a/components/core/util/public-api.ts +++ b/components/core/util/public-api.ts @@ -1,7 +1,6 @@ export * from './check'; export * from './convert'; export * from './getMentions'; -export * from './keycodes'; export * from './nz-global-monitor'; export * from './textarea-caret-position'; export * from './throttleByAnimationFrame'; diff --git a/components/icon/nz-icon.service.ts b/components/icon/nz-icon.service.ts index 88d6386be19..c835e90d69a 100644 --- a/components/icon/nz-icon.service.ts +++ b/components/icon/nz-icon.service.ts @@ -83,6 +83,7 @@ export const NZ_ICONS_USED_BY_ZORRO: IconDefinition[] = [ RightOutline, StarFill, SearchOutline, + StarFill, UploadOutline, UpOutline ]; diff --git a/components/rate/demo/text.ts b/components/rate/demo/text.ts index 43602620591..ea5a30fdea0 100644 --- a/components/rate/demo/text.ts +++ b/components/rate/demo/text.ts @@ -3,10 +3,11 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-rate-text', template: ` - - {{ value }} stars + + {{ value ? tooltips[ value - 1 ] : '' }} ` }) export class NzDemoRateTextComponent { + tooltips = [ 'terrible', 'bad', 'normal', 'good', 'wonderful' ]; value = 3; } diff --git a/components/rate/doc/index.en-US.md b/components/rate/doc/index.en-US.md index 017a2824e96..4087c162b87 100644 --- a/components/rate/doc/index.en-US.md +++ b/components/rate/doc/index.en-US.md @@ -23,6 +23,7 @@ Rate component. | `[nzCharacter]` | custom character of rate | `TemplateRef` | `` | | `[nzCount]` | star count | `number` | `5` | | `[nzDisabled]` | read only, unable to interact | `boolean` | `false` | +| `[nzTooltips]` | Customize tooltip by each character | `string[]` | `[]` | | `[ngModel]` | current value , double binding | `number` | - | | `(ngModelChange)` | callback when select value | `EventEmitter` | - | | `(nzOnBlur)` | callback when component lose focus | `EventEmitter` | - | diff --git a/components/rate/doc/index.zh-CN.md b/components/rate/doc/index.zh-CN.md index 8e10387cc80..0f7415c92b6 100644 --- a/components/rate/doc/index.zh-CN.md +++ b/components/rate/doc/index.zh-CN.md @@ -24,6 +24,7 @@ title: Rate | `[nzCharacter]` | 自定义字符 | `TemplateRef` | `` | | `[nzCount]` | star 总数 | `number` | `5` | | `[nzDisabled]` | 只读,无法进行交互 | `boolean` | `false` | +| `[nzTooltips]` | 自定义每项的提示信息 | `string[]` | `[]` | | `[ngModel]` | 当前数,可以双向绑定 | `number` | `0` | | `(ngModelChange)` | 当前数改变时的回调 | `EventEmitter` | - | | `(nzOnBlur)` | 失去焦点时的回调 | `EventEmitter` | - | diff --git a/components/rate/nz-rate-item.component.html b/components/rate/nz-rate-item.component.html new file mode 100644 index 00000000000..26ea416fa57 --- /dev/null +++ b/components/rate/nz-rate-item.component.html @@ -0,0 +1,16 @@ +
+ +
+
+ +
+ + + + diff --git a/components/rate/nz-rate-item.component.ts b/components/rate/nz-rate-item.component.ts new file mode 100644 index 00000000000..0428a6502aa --- /dev/null +++ b/components/rate/nz-rate-item.component.ts @@ -0,0 +1,23 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, TemplateRef, ViewEncapsulation } from '@angular/core'; +import { InputBoolean } from '../core/util'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + selector : '[nz-rate-item]', + templateUrl: './nz-rate-item.component.html' +}) +export class NzRateItemComponent { + @Input() character: TemplateRef; + @Input() @InputBoolean() allowHalf: boolean = false; + @Output() readonly itemHover = new EventEmitter(); + @Output() readonly itemClick = new EventEmitter(); + + hoverRate(isHalf: boolean): void { + this.itemHover.next(isHalf && this.allowHalf); + } + + clickRate(isHalf: boolean): void { + this.itemClick.next(isHalf && this.allowHalf); + } +} diff --git a/components/rate/nz-rate.component.html b/components/rate/nz-rate.component.html index 3bf5bae0ba0..1a18f522e74 100644 --- a/components/rate/nz-rate.component.html +++ b/components/rate/nz-rate.component.html @@ -1,23 +1,22 @@ - - - -
    -
  • +
  • -
    - -
    -
    - + nz-tooltip + [nzTitle]="nzTooltips[ i ]"> +
diff --git a/components/rate/nz-rate.component.ts b/components/rate/nz-rate.component.ts index 33c7deb59c2..3e1bf26e6e2 100644 --- a/components/rate/nz-rate.component.ts +++ b/components/rate/nz-rate.component.ts @@ -2,21 +2,29 @@ import { LEFT_ARROW, RIGHT_ARROW } from '@angular/cdk/keycodes'; import { forwardRef, AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, + OnChanges, OnInit, Output, Renderer2, + SimpleChanges, TemplateRef, - ViewChild + ViewChild, + ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { toBoolean } from '../core/util/convert'; +import { ClassMap } from '../core/interface/interface'; +import { InputBoolean } from '../core/util/convert'; @Component({ + changeDetection : ChangeDetectionStrategy.OnPush, + encapsulation : ViewEncapsulation.None, selector : 'nz-rate', preserveWhitespaces: false, templateUrl : './nz-rate.component.html', @@ -28,41 +36,31 @@ import { toBoolean } from '../core/util/convert'; } ] }) -export class NzRateComponent implements OnInit, ControlValueAccessor, AfterViewInit { - private _allowClear = true; - private _allowHalf = false; - private _disabled = false; - private _count = 5; - private _value = 0; - private _autoFocus = false; +export class NzRateComponent implements OnInit, ControlValueAccessor, AfterViewInit, OnChanges { + @ViewChild('ulElement') private ulElement: ElementRef; + + @Input() @InputBoolean() nzAllowClear: boolean = true; + @Input() @InputBoolean() nzAllowHalf: boolean = false; + @Input() @InputBoolean() nzDisabled: boolean = false; + @Input() @InputBoolean() nzAutoFocus: boolean = false; @Input() nzCharacter: TemplateRef; + @Input() nzTooltips: string[] = []; @Output() readonly nzOnBlur = new EventEmitter(); @Output() readonly nzOnFocus = new EventEmitter(); - @Output() readonly nzOnKeyDown = new EventEmitter(); @Output() readonly nzOnHoverChange = new EventEmitter(); - @ViewChild('ulElement') private ulElement: ElementRef; - prefixCls = 'ant-rate'; - isInit = false; + @Output() readonly nzOnKeyDown = new EventEmitter(); + + classMap: ClassMap; hasHalf = false; - innerPrefixCls = `${this.prefixCls}-star`; - classMap; - starArray: number[] = []; hoverValue = 0; + prefixCls = 'ant-rate'; + innerPrefixCls = `${this.prefixCls}-star`; isFocused = false; - floatReg: RegExp = /^\d+(\.\d+)?$/; - - onChange: (value: number) => void = () => null; - onTouched: () => void = () => null; - - @Input() - set nzAutoFocus(value: boolean) { - this._autoFocus = toBoolean(value); - this.updateAutoFocus(); - } + isInit = false; + starArray: number[] = []; - get nzAutoFocus(): boolean { - return this._autoFocus; - } + private _count = 5; + private _value = 0; @Input() set nzCount(value: number) { @@ -77,61 +75,24 @@ export class NzRateComponent implements OnInit, ControlValueAccessor, AfterViewI return this._count; } - @Input() - set nzAllowHalf(value: boolean) { - this._allowHalf = toBoolean(value); - } - - get nzAllowHalf(): boolean { - return this._allowHalf; - } - - @Input() - set nzAllowClear(value: boolean) { - this._allowClear = toBoolean(value); - } - - get nzAllowClear(): boolean { - return this._allowClear; - } - - get nzValue(): number { - return this._value; - } + get nzValue(): number { return this._value; } set nzValue(input: number) { - let value = input; - if (this._value === value) { + if (this._value === input) { return; } - this._value = value; - if (this.floatReg.test(value.toString())) { - value += 0.5; - this.hasHalf = true; - } - this.hoverValue = value; - } - @Input() - set nzDisabled(value: boolean) { - this._disabled = toBoolean(value); - this.setClassMap(); + this._value = input; + this.hasHalf = !Number.isInteger(input); + this.hoverValue = Math.ceil(input); } - get nzDisabled(): boolean { - return this._disabled; + constructor(private renderer: Renderer2, private cdr: ChangeDetectorRef) { } - setClassMap(): void { - this.classMap = { - [ this.prefixCls ] : true, - [ `${this.prefixCls}-disabled` ]: this.nzDisabled - }; - } - - updateAutoFocus(): void { - if (this.isInit && !this.nzDisabled) { - if (this.nzAutoFocus) { + ngOnChanges(changes: SimpleChanges): void { + if (changes.nzAutoFocus && !changes.nzAutoFocus.isFirstChange()) { + if (this.nzAutoFocus && !this.nzDisabled) { this.renderer.setAttribute(this.ulElement.nativeElement, 'autofocus', 'autofocus'); } else { this.renderer.removeAttribute(this.ulElement.nativeElement, 'autofocus'); @@ -139,19 +100,22 @@ export class NzRateComponent implements OnInit, ControlValueAccessor, AfterViewI } } - clickRate(e: MouseEvent, index: number, isFull: boolean): void { - e.stopPropagation(); + ngOnInit(): void { + this.updateStarArray(); + } + + ngAfterViewInit(): void { + this.isInit = true; + } + + onItemClick(index: number, isHalf: boolean): void { if (this.nzDisabled) { return; } - this.hasHalf = !isFull && this.nzAllowHalf; - let actualValue = index + 1; - this.hoverValue = actualValue; + this.hoverValue = index + 1; - if (this.hasHalf) { - actualValue -= 0.5; - } + const actualValue = isHalf ? index + 0.5 : index + 1; if (this.nzValue === actualValue) { if (this.nzAllowClear) { @@ -164,29 +128,20 @@ export class NzRateComponent implements OnInit, ControlValueAccessor, AfterViewI } } - hoverRate(e: MouseEvent, index: number, isFull: boolean): void { - e.stopPropagation(); - if (this.nzDisabled) { - return; - } - const isHalf: boolean = !isFull && this.nzAllowHalf; - if (this.hoverValue === index + 1 && isHalf === this.hasHalf) { + onItemHover(index: number, isHalf: boolean): void { + if (this.nzDisabled || + (this.hoverValue === index + 1 && isHalf === this.hasHalf)) { return; } this.hoverValue = index + 1; - this.nzOnHoverChange.emit(this.hoverValue); this.hasHalf = isHalf; + this.nzOnHoverChange.emit(this.hoverValue); } - leaveRate(e: MouseEvent): void { - e.stopPropagation(); - let oldVal = this.nzValue; - if (this.floatReg.test(oldVal.toString())) { - oldVal += 0.5; - this.hasHalf = true; - } - this.hoverValue = oldVal; + onRateLeave(): void { + this.hasHalf = !Number.isInteger(this.nzValue); + this.hoverValue = Math.ceil(this.nzValue); } onFocus(e: FocusEvent): void { @@ -208,29 +163,23 @@ export class NzRateComponent implements OnInit, ControlValueAccessor, AfterViewI } onKeyDown(e: KeyboardEvent): void { - const code = e.code; - if ((code === 'ArrowRight' || e.keyCode === RIGHT_ARROW) && (this.nzValue < this.nzCount)) { - if (this.nzAllowHalf) { - this.nzValue += 0.5; - } else { - this.nzValue += 1; - } - this.onChange(this.nzValue); - } else if ((code === 'ArrowLeft' || e.keyCode === LEFT_ARROW) && (this.nzValue > 0)) { - if (this.nzAllowHalf) { - this.nzValue -= 0.5; - } else { - this.nzValue -= 1; - } + const oldVal = this.nzValue; + + if (e.keyCode === RIGHT_ARROW && (this.nzValue < this.nzCount)) { + this.nzValue += this.nzAllowHalf ? 0.5 : 1; + } else if (e.keyCode === LEFT_ARROW && (this.nzValue > 0)) { + this.nzValue -= this.nzAllowHalf ? 0.5 : 1; + } + + if (oldVal !== this.nzValue) { this.onChange(this.nzValue); + this.nzOnKeyDown.emit(e); + this.cdr.markForCheck(); } - this.nzOnKeyDown.emit(e); - e.preventDefault(); } setClasses(i: number): object { return { - [ this.innerPrefixCls ] : true, [ `${this.innerPrefixCls}-full` ] : (i + 1 < this.hoverValue) || (!this.hasHalf) && (i + 1 === this.hoverValue), [ `${this.innerPrefixCls}-half` ] : (this.hasHalf) && (i + 1 === this.hoverValue), [ `${this.innerPrefixCls}-active` ] : (this.hasHalf) && (i + 1 === this.hoverValue), @@ -239,16 +188,19 @@ export class NzRateComponent implements OnInit, ControlValueAccessor, AfterViewI }; } - updateStarArray(): void { - let index = 0; - this.starArray = []; - while (index < this.nzCount) { - this.starArray.push(index++); - } + private updateStarArray(): void { + this.starArray = Array(this.nzCount).fill(0).map((_, i) => i); } + // #region Implement `ControlValueAccessor` + writeValue(value: number | null): void { this.nzValue = value || 0; + this.cdr.markForCheck(); + } + + setDisabledState(isDisabled: boolean): void { + this.nzDisabled = isDisabled; } registerOnChange(fn: (_: number) => void): void { @@ -259,19 +211,8 @@ export class NzRateComponent implements OnInit, ControlValueAccessor, AfterViewI this.onTouched = fn; } - setDisabledState(isDisabled: boolean): void { - this.nzDisabled = isDisabled; - } - - constructor(private renderer: Renderer2) { - } - - ngOnInit(): void { - this.setClassMap(); - this.updateStarArray(); - } + onChange: (value: number) => void = () => null; + onTouched: () => void = () => null; - ngAfterViewInit(): void { - this.isInit = true; - } + // #endregion } diff --git a/components/rate/nz-rate.module.ts b/components/rate/nz-rate.module.ts index 98578846426..3672d82bff0 100644 --- a/components/rate/nz-rate.module.ts +++ b/components/rate/nz-rate.module.ts @@ -2,12 +2,15 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NzIconModule } from '../icon/nz-icon.module'; +import { NzToolTipModule } from '../tooltip/nz-tooltip.module'; + +import { NzRateItemComponent } from './nz-rate-item.component'; import { NzRateComponent } from './nz-rate.component'; @NgModule({ exports : [ NzRateComponent ], - declarations: [ NzRateComponent ], - imports : [ CommonModule, NzIconModule ] + declarations: [ NzRateComponent, NzRateItemComponent ], + imports : [ CommonModule, NzIconModule, NzToolTipModule ] }) export class NzRateModule { } diff --git a/components/rate/nz-rate.spec.ts b/components/rate/nz-rate.spec.ts index a3cdb23d95c..79349815225 100644 --- a/components/rate/nz-rate.spec.ts +++ b/components/rate/nz-rate.spec.ts @@ -1,10 +1,11 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, DebugElement, ViewChild } from '@angular/core'; import { fakeAsync, flush, TestBed } from '@angular/core/testing'; import { FormsModule, FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; -import { dispatchEvent, dispatchFakeEvent } from '../core/testing'; +import { dispatchEvent, dispatchFakeEvent, dispatchKeyboardEvent } from '../core/testing'; +import { LEFT_ARROW, RIGHT_ARROW } from '@angular/cdk/keycodes'; import { NzRateComponent } from './nz-rate.component'; import { NzRateModule } from './nz-rate.module'; @@ -44,7 +45,7 @@ describe('rate', () => { it('should click work', fakeAsync(() => { fixture.detectChanges(); expect(testComponent.value).toBe(0); - rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.click(); + rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.firstElementChild.click(); fixture.detectChanges(); flush(); fixture.detectChanges(); @@ -55,7 +56,7 @@ describe('rate', () => { testComponent.allowHalf = true; fixture.detectChanges(); expect(testComponent.value).toBe(0); - rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.click(); + rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.children[ 1 ].click(); fixture.detectChanges(); flush(); fixture.detectChanges(); @@ -66,13 +67,13 @@ describe('rate', () => { testComponent.allowClear = false; fixture.detectChanges(); expect(testComponent.value).toBe(0); - rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.click(); + rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.firstElementChild.click(); fixture.detectChanges(); flush(); fixture.detectChanges(); expect(testComponent.value).toBe(4); expect(testComponent.modelChange).toHaveBeenCalledTimes(1); - rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.click(); + rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.firstElementChild.click(); fixture.detectChanges(); flush(); fixture.detectChanges(); @@ -80,7 +81,7 @@ describe('rate', () => { expect(testComponent.modelChange).toHaveBeenCalledTimes(1); testComponent.allowClear = true; fixture.detectChanges(); - rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.click(); + rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.firstElementChild.click(); fixture.detectChanges(); flush(); fixture.detectChanges(); @@ -91,7 +92,7 @@ describe('rate', () => { testComponent.disabled = true; fixture.detectChanges(); expect(testComponent.value).toBe(0); - rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.click(); + rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.firstElementChild.click(); fixture.detectChanges(); flush(); fixture.detectChanges(); @@ -102,7 +103,7 @@ describe('rate', () => { fixture.detectChanges(); expect(rate.nativeElement.firstElementChild.children.length).toBe(5); expect(testComponent.value).toBe(0); - rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.click(); + rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.firstElementChild.click(); fixture.detectChanges(); flush(); fixture.detectChanges(); @@ -137,7 +138,7 @@ describe('rate', () => { }); it('should hover rate work', () => { fixture.detectChanges(); - dispatchFakeEvent(rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild, 'mouseover'); + dispatchFakeEvent(rate.nativeElement.firstElementChild.children[ 3 ].firstElementChild.firstElementChild, 'mouseover'); fixture.detectChanges(); expect(rate.nativeElement.firstElementChild.children[ 3 ].classList).toContain('ant-rate-star-full'); expect(testComponent.onHoverChange).toHaveBeenCalledWith(4); @@ -154,46 +155,37 @@ describe('rate', () => { expect(testComponent.onHoverChange).toHaveBeenCalledTimes(1); }); it('should keydown work', () => { - const leftArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowLeft' - }); - const rightArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowRight' - }); fixture.detectChanges(); expect(testComponent.value).toBe(0); - dispatchEvent(rate.nativeElement.firstElementChild, leftArrowEvent); + dispatchKeyboardEvent(rate.nativeElement.firstElementChild, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(testComponent.value).toBe(0); expect(testComponent.modelChange).toHaveBeenCalledTimes(0); - dispatchEvent(rate.nativeElement.firstElementChild, rightArrowEvent); + dispatchKeyboardEvent(rate.nativeElement.firstElementChild, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(testComponent.value).toBe(1); expect(testComponent.modelChange).toHaveBeenCalledTimes(1); - dispatchEvent(rate.nativeElement.firstElementChild, leftArrowEvent); + dispatchKeyboardEvent(rate.nativeElement.firstElementChild, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(testComponent.value).toBe(0); expect(testComponent.modelChange).toHaveBeenCalledTimes(2); testComponent.allowHalf = true; fixture.detectChanges(); - dispatchEvent(rate.nativeElement.firstElementChild, rightArrowEvent); + dispatchKeyboardEvent(rate.nativeElement.firstElementChild, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(testComponent.value).toBe(0.5); expect(testComponent.modelChange).toHaveBeenCalledTimes(3); - dispatchEvent(rate.nativeElement.firstElementChild, leftArrowEvent); + dispatchKeyboardEvent(rate.nativeElement.firstElementChild, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(testComponent.value).toBe(0); expect(testComponent.modelChange).toHaveBeenCalledTimes(4); }); - it('should right keydown work', fakeAsync(() => { - const rightArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowRight' - }); + it('should right keydown not dispatch change reached limit', fakeAsync(() => { testComponent.value = 5; fixture.detectChanges(); flush(); fixture.detectChanges(); - dispatchEvent(rate.nativeElement.firstElementChild, rightArrowEvent); + dispatchKeyboardEvent(rate.nativeElement.firstElementChild, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(testComponent.value).toBe(5); expect(testComponent.modelChange).toHaveBeenCalledTimes(0); @@ -220,7 +212,7 @@ describe('rate', () => { it('should set disabled work', fakeAsync(() => { flush(); expect(testComponent.formGroup.get('rate').value).toBe(1); - rate.nativeElement.firstElementChild.children[3].firstElementChild.click(); + rate.nativeElement.firstElementChild.children[3].firstElementChild.firstElementChild.click(); fixture.detectChanges(); expect(testComponent.formGroup.get('rate').value).toBe(4); fixture.detectChanges(); @@ -231,7 +223,7 @@ describe('rate', () => { fixture.detectChanges(); flush(); fixture.detectChanges(); - rate.nativeElement.firstElementChild.children[3].firstElementChild.click(); + rate.nativeElement.firstElementChild.children[3].firstElementChild.firstElementChild.click(); fixture.detectChanges(); expect(testComponent.formGroup.get('rate').value).toBe(2); }));