diff --git a/components/mention/demo/async.ts b/components/mention/demo/async.ts index 7362c97225..06c1757d1b 100755 --- a/components/mention/demo/async.ts +++ b/components/mention/demo/async.ts @@ -7,7 +7,7 @@ import { MentionOnSearchTypes } from 'ng-zorro-antd/mention'; encapsulation: ViewEncapsulation.None, template: ` - + ` }) diff --git a/components/mention/demo/avatar.md b/components/mention/demo/avatar.md index ec5e19efc7..576260ae02 100755 --- a/components/mention/demo/avatar.md +++ b/components/mention/demo/avatar.md @@ -1,5 +1,5 @@ --- -order: 3 +order: 6 title: zh-CN: 头像 en-US: Icon Image diff --git a/components/mention/demo/avatar.ts b/components/mention/demo/avatar.ts index 5629b6abed..fa7dc0e07c 100755 --- a/components/mention/demo/avatar.ts +++ b/components/mention/demo/avatar.ts @@ -5,7 +5,7 @@ import { Component, ViewEncapsulation } from '@angular/core'; encapsulation: ViewEncapsulation.None, template: ` - + {{ framework.name }} - {{ framework.type }} diff --git a/components/mention/demo/basic.ts b/components/mention/demo/basic.ts index f3d3d83979..1974ac8b52 100644 --- a/components/mention/demo/basic.ts +++ b/components/mention/demo/basic.ts @@ -5,13 +5,14 @@ import { Component, ViewEncapsulation } from '@angular/core'; encapsulation: ViewEncapsulation.None, template: ` - + > ` }) diff --git a/components/mention/demo/custom-tag.md b/components/mention/demo/custom-tag.md index 17d3f5c906..358a912fa7 100755 --- a/components/mention/demo/custom-tag.md +++ b/components/mention/demo/custom-tag.md @@ -1,5 +1,5 @@ --- -order: 2 +order: 7 title: zh-CN: 自定义建议 en-US: Customize Suggestion diff --git a/components/mention/demo/custom-tag.ts b/components/mention/demo/custom-tag.ts index 982c26ccc5..efd8dd91d4 100644 --- a/components/mention/demo/custom-tag.ts +++ b/components/mention/demo/custom-tag.ts @@ -5,7 +5,7 @@ import { Component, ViewEncapsulation } from '@angular/core'; encapsulation: ViewEncapsulation.None, template: ` - + {{ framework.name }} - {{ framework.type }} diff --git a/components/mention/demo/controlled.md b/components/mention/demo/form.md similarity index 95% rename from components/mention/demo/controlled.md rename to components/mention/demo/form.md index aef355b3ed..d074b04916 100755 --- a/components/mention/demo/controlled.md +++ b/components/mention/demo/form.md @@ -1,5 +1,5 @@ --- -order: 4 +order: 2 title: zh-CN: 配合 Form 使用 en-US: With Form diff --git a/components/mention/demo/controlled.ts b/components/mention/demo/form.ts similarity index 87% rename from components/mention/demo/controlled.ts rename to components/mention/demo/form.ts index c74bc8da80..435d3f3950 100644 --- a/components/mention/demo/controlled.ts +++ b/components/mention/demo/form.ts @@ -4,7 +4,7 @@ import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from import { NzMentionComponent } from 'ng-zorro-antd/mention'; @Component({ - selector: 'nz-demo-mention-controlled', + selector: 'nz-demo-mention-form', encapsulation: ViewEncapsulation.None, template: `
@@ -12,7 +12,14 @@ import { NzMentionComponent } from 'ng-zorro-antd/mention'; Top coders - + @@ -26,7 +33,7 @@ import { NzMentionComponent } from 'ng-zorro-antd/mention';
` }) -export class NzDemoMentionControlledComponent implements OnInit { +export class NzDemoMentionFormComponent implements OnInit { suggestions = ['afc163', 'benjycui', 'yiminghe', 'RaoHai', '中文', 'にほんご']; validateForm!: FormGroup; @ViewChild('mentions', { static: true }) mentionChild!: NzMentionComponent; diff --git a/components/mention/demo/multilines.md b/components/mention/demo/multilines.md index 4692c9b92d..fbfe4545ba 100755 --- a/components/mention/demo/multilines.md +++ b/components/mention/demo/multilines.md @@ -1,5 +1,5 @@ --- -order: 5 +order: 8 title: zh-CN: 多行 en-US: Multi-lines Mode diff --git a/components/mention/demo/placement.md b/components/mention/demo/placement.md index ef14ea14a7..4dcc1e58f4 100755 --- a/components/mention/demo/placement.md +++ b/components/mention/demo/placement.md @@ -1,5 +1,5 @@ --- -order: 0 +order: 5 title: zh-CN: 向上展开 en-US: Placement diff --git a/components/mention/demo/placement.ts b/components/mention/demo/placement.ts index da6c35b25f..5ec8b3d7eb 100755 --- a/components/mention/demo/placement.ts +++ b/components/mention/demo/placement.ts @@ -5,7 +5,13 @@ import { Component, ViewEncapsulation } from '@angular/core'; encapsulation: ViewEncapsulation.None, template: ` - + ` }) diff --git a/components/mention/demo/multiple-trigger.md b/components/mention/demo/prefix.md similarity index 96% rename from components/mention/demo/multiple-trigger.md rename to components/mention/demo/prefix.md index e1467470fb..cc27d3d295 100755 --- a/components/mention/demo/multiple-trigger.md +++ b/components/mention/demo/prefix.md @@ -1,5 +1,5 @@ --- -order: 8 +order: 3 title: zh-CN: 自定义触发字符 en-US: Customize Trigger Token diff --git a/components/mention/demo/multiple-trigger.ts b/components/mention/demo/prefix.ts similarity index 86% rename from components/mention/demo/multiple-trigger.ts rename to components/mention/demo/prefix.ts index 818e1ca0fc..3040d39ce7 100755 --- a/components/mention/demo/multiple-trigger.ts +++ b/components/mention/demo/prefix.ts @@ -3,20 +3,21 @@ import { Component, ViewEncapsulation } from '@angular/core'; import { MentionOnSearchTypes } from 'ng-zorro-antd/mention'; @Component({ - selector: 'nz-demo-mention-multiple-trigger', + selector: 'nz-demo-mention-prefix', encapsulation: ViewEncapsulation.None, template: ` - + > ` }) -export class NzDemoMentionMultipleTriggerComponent { +export class NzDemoMentionPrefixComponent { inputValue?: string; suggestions: string[] = []; users = ['afc163', 'benjycui', 'yiminghe', 'RaoHai', '中文', 'にほんご']; diff --git a/components/mention/demo/readonly.md b/components/mention/demo/readonly.md index e867f2d627..b45a2e2708 100755 --- a/components/mention/demo/readonly.md +++ b/components/mention/demo/readonly.md @@ -1,5 +1,5 @@ --- -order: 7 +order: 4 title: zh-CN: 无效或只读 en-US: disabled or readOnly diff --git a/components/mention/demo/readonly.ts b/components/mention/demo/readonly.ts index 6561331f56..7a0d36e8f9 100755 --- a/components/mention/demo/readonly.ts +++ b/components/mention/demo/readonly.ts @@ -4,16 +4,25 @@ import { Component, ViewEncapsulation } from '@angular/core'; selector: 'nz-demo-mention-readonly', encapsulation: ViewEncapsulation.None, template: ` - - + + + + ` }) diff --git a/components/mention/demo/status.md b/components/mention/demo/status.md new file mode 100644 index 0000000000..e1f17dc78f --- /dev/null +++ b/components/mention/demo/status.md @@ -0,0 +1,14 @@ +--- +order: 10 +title: + zh-CN: 自定义状态 + en-US: Status +--- + +## zh-CN + +使用 `nzStatus` 为 Mentions 添加状态。可选 `error` 或者 `warning`。 + +## en-US + +Add status to Mentions with `nzStatus`, which could be `error` or `warning`。 diff --git a/components/mention/demo/status.ts b/components/mention/demo/status.ts new file mode 100644 index 0000000000..3175ced4d0 --- /dev/null +++ b/components/mention/demo/status.ts @@ -0,0 +1,18 @@ +import { Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'nz-demo-mention-status', + encapsulation: ViewEncapsulation.None, + template: ` + + + + + + + ` +}) +export class NzDemoMentionStatusComponent { + inputValue: string = '@afc163'; + suggestions = ['afc163', 'benjycui', 'yiminghe', 'RaoHai', '中文', 'にほんご']; +} diff --git a/components/mention/doc/index.en-US.md b/components/mention/doc/index.en-US.md index bddbfe4647..03b713eaa6 100644 --- a/components/mention/doc/index.en-US.md +++ b/components/mention/doc/index.en-US.md @@ -38,6 +38,7 @@ import { NzMentionModule } from 'ng-zorro-antd/mention'; | `[nzPlacement]` | The position of the suggestion relative to the target, which can be one of top and bottom | `'button' \| 'top'` | `'bottom'` | | `[nzPrefix]` | Character which will trigger Mention to show mention list | `string \| string[]` | `'@'` | | `[nzSuggestions]` | Suggestion content | `any[]` | `[]` | +| `[nzStatus]` | Set validation status | `'error' \| 'warning'` | - | | `[nzValueWith]` | Function that maps an suggestion's value | `(any) => string \| (value: string) => string` | | `(nzOnSelect)` | Callback function called when select from suggestions | `EventEmitter` | - | | `(nzOnSearchChange)` | Callback function called when search content changes| `EventEmitter` | - | diff --git a/components/mention/doc/index.zh-CN.md b/components/mention/doc/index.zh-CN.md index 5f7cd3c779..b1b666ee98 100644 --- a/components/mention/doc/index.zh-CN.md +++ b/components/mention/doc/index.zh-CN.md @@ -39,6 +39,7 @@ import { NzMentionModule } from 'ng-zorro-antd/mention'; | `[nzPlacement]` | 建议框位置 | `'bottom' \| 'top'` | `'bottom'` | | `[nzPrefix]` | 触发弹出下拉框的字符 | `string \| string[]` | `'@'` | | `[nzSuggestions]` | 建议内容 | `any[]` | `[]` | +| `[nzStatus]` | 设置校验状态 | `'error' \| 'warning'` | - | | `[nzValueWith]` | 建议选项的取值方法 | `(any) => string \| (value: string) => string` | | `(nzOnSelect)` | 下拉框选择建议时回调 | `EventEmitter` | - | | `(nzOnSearchChange)` | 输入框中 @ 变化时回调 | `EventEmitter` | - | diff --git a/components/mention/mention.component.ts b/components/mention/mention.component.ts index 489584c59e..d38d13ce78 100644 --- a/components/mention/mention.component.ts +++ b/components/mention/mention.component.ts @@ -32,6 +32,7 @@ import { Optional, Output, QueryList, + Renderer2, SimpleChanges, TemplateRef, ViewChild, @@ -43,8 +44,8 @@ import { startWith, switchMap, takeUntil } from 'rxjs/operators'; import { DEFAULT_MENTION_BOTTOM_POSITIONS, DEFAULT_MENTION_TOP_POSITIONS } from 'ng-zorro-antd/core/overlay'; import { NzDestroyService } from 'ng-zorro-antd/core/services'; -import { BooleanInput, NzSafeAny } from 'ng-zorro-antd/core/types'; -import { getCaretCoordinates, getMentions, InputBoolean } from 'ng-zorro-antd/core/util'; +import { BooleanInput, NgClassInterface, NzSafeAny, NzStatus } from 'ng-zorro-antd/core/types'; +import { getCaretCoordinates, getMentions, getStatusClassNames, InputBoolean } from 'ng-zorro-antd/core/util'; import { NZ_MENTION_CONFIG } from './config'; import { NzMentionSuggestionDirective } from './mention-suggestions'; @@ -117,6 +118,7 @@ export class NzMentionComponent implements OnDestroy, OnInit, AfterViewInit, OnC @Input() nzNotFoundContent: string = '无匹配结果,轻敲空格完成输入'; @Input() nzPlacement: MentionPlacement = 'bottom'; @Input() nzSuggestions: NzSafeAny[] = []; + @Input() nzStatus?: NzStatus; @Output() readonly nzOnSelect: EventEmitter = new EventEmitter(); @Output() readonly nzOnSearchChange: EventEmitter = new EventEmitter(); @@ -137,6 +139,10 @@ export class NzMentionComponent implements OnDestroy, OnInit, AfterViewInit, OnC suggestionTemplate: TemplateRef<{ $implicit: NzSafeAny }> | null = null; activeIndex = -1; dir: Direction = 'ltr'; + // status + prefixCls: string = 'ant-mentions'; + statusCls: NgClassInterface = {}; + nzHasFeedback: boolean = false; private previousValue: string | null = null; private cursorMention: string | null = null; @@ -166,6 +172,8 @@ export class NzMentionComponent implements OnDestroy, OnInit, AfterViewInit, OnC private cdr: ChangeDetectorRef, private overlay: Overlay, private viewContainerRef: ViewContainerRef, + private elementRef: ElementRef, + private renderer: Renderer2, private nzMentionService: NzMentionService, private destroy$: NzDestroyService ) {} @@ -185,13 +193,17 @@ export class NzMentionComponent implements OnDestroy, OnInit, AfterViewInit, OnC } ngOnChanges(changes: SimpleChanges): void { - if (changes.hasOwnProperty('nzSuggestions')) { + const { nzSuggestions, nzStatus } = changes; + if (nzSuggestions) { if (this.isOpen) { this.previousValue = null; this.activeIndex = -1; this.resetDropdown(false); } } + if (nzStatus) { + this.setStatusStyles(); + } } ngAfterViewInit(): void { @@ -464,4 +476,16 @@ export class NzMentionComponent implements OnDestroy, OnInit, AfterViewInit, OnC .withPush(false); return this.positionStrategy; } + + private setStatusStyles(): void { + // render status if nzStatus is set + this.statusCls = getStatusClassNames(this.prefixCls, this.nzStatus, this.nzHasFeedback); + Object.keys(this.statusCls).forEach(status => { + if (this.statusCls[status]) { + this.renderer.addClass(this.elementRef.nativeElement, status); + } else { + this.renderer.removeClass(this.elementRef.nativeElement, status); + } + }); + } } diff --git a/components/mention/nz-mention.spec.ts b/components/mention/nz-mention.spec.ts index 5ecbc80f7c..64f17f3bc7 100644 --- a/components/mention/nz-mention.spec.ts +++ b/components/mention/nz-mention.spec.ts @@ -2,7 +2,7 @@ import { BidiModule, Direction, Directionality } from '@angular/cdk/bidi'; import { DOWN_ARROW, ENTER, ESCAPE, RIGHT_ARROW, TAB, UP_ARROW } from '@angular/cdk/keycodes'; import { OverlayContainer } from '@angular/cdk/overlay'; import { ScrollDispatcher } from '@angular/cdk/scrolling'; -import { ApplicationRef, Component, NgZone, ViewChild } from '@angular/core'; +import { ApplicationRef, Component, DebugElement, NgZone, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; @@ -16,6 +16,7 @@ import { MockNgZone, typeInElement } from 'ng-zorro-antd/core/testing'; +import { NzStatus } from 'ng-zorro-antd/core/types'; import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; import { NzInputModule } from '../input'; @@ -42,7 +43,12 @@ describe('mention', () => { ReactiveFormsModule, NzIconTestModule ], - declarations: [NzTestSimpleMentionComponent, NzTestPropertyMentionComponent, NzTestDirMentionComponent], + declarations: [ + NzTestSimpleMentionComponent, + NzTestPropertyMentionComponent, + NzTestDirMentionComponent, + NzTestStatusMentionComponent + ], providers: [ { provide: Directionality, useFactory: () => ({ value: dir }) }, { provide: ScrollDispatcher, useFactory: () => ({ scrolled: () => scrolledSubject }) }, @@ -199,15 +205,13 @@ describe('mention', () => { it('should support switch trigger', fakeAsync(() => { fixture.componentInstance.inputTrigger = true; fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')).nativeElement; + const textareaWithSingleLine = fixture.debugElement.query(By.css('textarea')).nativeElement; const mention = fixture.componentInstance.mention; + expect(textareaWithSingleLine).toBeTruthy(); - expect(fixture.debugElement.query(By.css('textarea'))).toBeFalsy(); - expect(input).toBeTruthy(); - - input.value = '@a'; + textareaWithSingleLine.value = '@a'; fixture.detectChanges(); - dispatchFakeEvent(input, 'click'); + dispatchFakeEvent(textareaWithSingleLine, 'click'); fixture.detectChanges(); flush(); @@ -521,6 +525,30 @@ describe('mention', () => { expect(fixture.componentInstance.mention.getMentions().join(',')).toBe('@Angular,@ant-design,@你好,@@ng,#ng'); }); }); + + describe('status', () => { + let fixture: ComponentFixture; + let mention: DebugElement; + + beforeEach(() => { + fixture = TestBed.createComponent(NzTestStatusMentionComponent); + mention = fixture.debugElement.query(By.directive(NzMentionComponent)); + fixture.detectChanges(); + }); + + it('should className with status correct', () => { + fixture.detectChanges(); + expect(mention.nativeElement.classList).toContain('ant-mentions-status-error'); + + fixture.componentInstance.status = 'warning'; + fixture.detectChanges(); + expect(mention.nativeElement.classList).toContain('ant-mentions-status-warning'); + + fixture.componentInstance.status = ''; + fixture.detectChanges(); + expect(mention.nativeElement.classList).not.toContain('ant-mentions-status-warning'); + }); + }); }); @Component({ @@ -533,7 +561,7 @@ describe('mention', () => { [(ngModel)]="inputValue" nzMentionTrigger > - +
` }) @@ -605,7 +633,7 @@ class NzTestPropertyMentionComponent { template: `
- +
` @@ -613,3 +641,14 @@ class NzTestPropertyMentionComponent { class NzTestDirMentionComponent { direction: Direction = 'ltr'; } + +@Component({ + template: ` + + + + ` +}) +class NzTestStatusMentionComponent { + status: NzStatus = 'error'; +} diff --git a/components/mention/style/patch.less b/components/mention/style/patch.less index aebb4eb1c6..fe3269fd18 100644 --- a/components/mention/style/patch.less +++ b/components/mention/style/patch.less @@ -1,21 +1,30 @@ -.ant-mentions { - border-width: 0; - display: unset; // to make sure input:hover work +.@{mention-prefix-cls} { + &-dropdown { + top: 100%; + left: 12px; + position: relative; + width: 100%; + margin-top: 8px; + margin-bottom: 4px; + } - &:hover { - border-right-width: 0; + &:focus-within { + .active(); } - & > textarea { - border: @border-width-base @border-style-base @border-color-base; + &&-status-error { + &:not(.@{mention-prefix-cls}-disabled):not(.@{mention-prefix-cls}-borderless).@{mention-prefix-cls} { + &:focus-within { + .active(@error-color, @error-color-hover, @error-color-outline); + } + } } -} -.ant-mentions-dropdown { - top: 100%; - left: 12px; - position: relative; - width: 100%; - margin-top: 4px; - margin-bottom: 4px; + &&-status-warning { + &:not(.@{mention-prefix-cls}-disabled):not(.@{mention-prefix-cls}-borderless).@{mention-prefix-cls} { + &:focus-within { + .active(@warning-color, @warning-color-hover, @warning-color-outline); + } + } + } }