import { AfterContentInit , AfterViewInit , ChangeDetectionStrategy , ChangeDetectorRef , Component , ContentChild , ElementRef , Host , Input , OnChanges , OnDestroy , Optional , Renderer2 , TemplateRef , ViewChild , ViewEncapsulation } from '@angular/core' ; import { FormControlName , NgModel , RequiredValidator , Validator } from '@angular/forms' ; import { Subject } from 'rxjs' ; import { filter , takeUntil } from 'rxjs/operators' ; import { ResponsiveService } from '@delon/theme' ; import { isEmpty } from '@delon/util/browser' ; import { BooleanInput , InputBoolean , InputNumber , NumberInput } from '@delon/util/decorator' ; import { helpMotion } from 'ng-zorro-antd/core/animation' ; import type { NzSafeAny } from 'ng-zorro-antd/core/types' ; import { SEContainerComponent } from './se-container.component' ; import { SEError , SEErrorType } from './se.types' ; const prefixCls = `se` ; let nextUniqueId = 0 ; @ Component ( { selector : 'se' , exportAs : 'se' , templateUrl : './se.component.html' , host : { '[style.padding-left.px]' : 'paddingValue' , '[style.padding-right.px]' : 'paddingValue' , '[class.se__hide-label]' : 'hideLabel' , '[class.ant-form-item-has-error]' : 'invalid' , '[class.ant-form-item-with-help]' : 'showErr' } , preserveWhitespaces : false , animations : [ helpMotion ] , changeDetection : ChangeDetectionStrategy . OnPush , encapsulation : ViewEncapsulation . None } ) export class SEComponent implements OnChanges , AfterContentInit , AfterViewInit , OnDestroy { static ngAcceptInputType_col : NumberInput ; static ngAcceptInputType_required : BooleanInput ; static ngAcceptInputType_line : BooleanInput ; static ngAcceptInputType_labelWidth : NumberInput ; static ngAcceptInputType_noColon : BooleanInput ; static ngAcceptInputType_hideLabel : BooleanInput ; private el : HTMLElement ; private unsubscribe$ = new Subject < void > ( ) ; @ ContentChild ( NgModel , { static : true } ) private readonly ngModel ? : NgModel ; @ ContentChild ( FormControlName , { static : true } ) private readonly formControlName ? : FormControlName ; @ ViewChild ( 'contentElement' , { static : true } ) private readonly contentElement ! : ElementRef ; private clsMap : string [ ] = [ ] ; private inited = false ; private onceFlag = false ; private errorData : SEError = { } ; private isBindModel = false ; invalid = false ; _labelWidth : number | null = null ; _noColon : boolean | null = null ; _error ? : string | TemplateRef < void > ; @ Input ( ) optional ? : string | TemplateRef < void > | null = null ; @ Input ( ) optionalHelp ? : string | TemplateRef < void > | null = null ; @ Input ( ) optionalHelpColor ? : string ; @ Input ( ) set error ( val ) { this . errorData = typeof val === 'string' || val instanceof TemplateRef ? { '' : val } : val ; } @ Input ( ) extra ? : string | TemplateRef < void > | null ; @ Input ( ) label ? : string | TemplateRef < void > | null ; @ Input ( ) @ InputNumber ( null ) col ? : number | null ; @ Input ( ) @ InputBoolean ( ) required = false ; @ Input ( ) controlClass ? : string | null = '' ; @ Input ( ) @ InputBoolean ( null ) line ? : boolean | null ; @ Input ( ) @ InputNumber ( null ) labelWidth ? : number | null ; @ Input ( ) @ InputBoolean ( null ) noColon ? : boolean | null ; @ Input ( ) @ InputBoolean ( ) hideLabel = false ; @ Input ( ) set id ( value ) { this . _id = value ; this . _autoId = false ; } _id = `_se-${ ++ nextUniqueId }` ; _autoId = true ; get paddingValue ( ) : number { return ( this . parent . gutter as number ) / 2 ; } get showErr ( ) : boolean { return this . invalid && ! ! this . _error && ! this . compact ; } get compact ( ) : boolean { return this . parent . size === 'compact' ; } private get ngControl ( ) : NgModel | FormControlName | null | undefined { return this . ngModel || this . formControlName ; } constructor ( el , @ Optional ( ) @ Host ( ) private parent , private rep , private ren , private cdr ) { if ( parent == null ) { throw new Error ( `[se] must include 'se-container' component` ) ; } this . el = el . nativeElement ; parent . errorNotify . pipe ( takeUntil ( this . unsubscribe$ ) , filter ( w => this . inited && this . ngControl != null && this . ngControl . name === w . name ) ) . subscribe ( item => { this . error = item . error ; this . updateStatus ( this . ngControl ! . invalid ! ) ; } ) ; } private setClass ( ) { const { el , ren , clsMap , col , parent , cdr , line , labelWidth , rep , noColon } = this ; this . _noColon = noColon != null ? noColon : parent . noColon ; this . _labelWidth = parent . nzLayout === 'horizontal' ? ( labelWidth != null ? labelWidth : parent . labelWidth ) : null ; clsMap . forEach ( cls => ren . removeClass ( el , cls ) ) ; clsMap . length = 0 ; const repCls = parent . nzLayout === 'horizontal' ? rep . genCls ( col != null ? col : parent . colInCon || parent . col ) : [ ] ; clsMap . push ( `ant-form-item` , ... repCls , `${ prefixCls }__item` ) ; if ( line || parent . line ) { clsMap . push ( `${ prefixCls }__line` ) ; } clsMap . forEach ( cls => ren . addClass ( el , cls ) ) ; cdr . detectChanges ( ) ; return this ; } private bindModel ( ) { if ( ! this . ngControl || this . isBindModel ) return ; this . isBindModel = true ; this . ngControl . statusChanges ! . pipe ( takeUntil ( this . unsubscribe$ ) ) . subscribe ( res => this . updateStatus ( res === 'INVALID' ) ) ; if ( this . _autoId ) { const controlAccessor = this . ngControl . valueAccessor as NzSafeAny ; const control = ( controlAccessor ?. elementRef || controlAccessor ?. _elementRef ) ?. nativeElement as HTMLElement ; if ( ! ! control ) { if ( control . id ) { this . _id = control . id ; } else { control . id = this . _id ; } } } if ( this . required !== true ) { const rawValidators = ( this . ngControl as NzSafeAny ) ?. _rawValidators as Validator [ ] ; this . required = rawValidators . find ( w => w instanceof RequiredValidator ) != null ; this . cdr . detectChanges ( ) ; } } private updateStatus ( invalid ) { if ( this . ngControl ?. disabled || this . ngControl ?. isDisabled ) { return ; } this . invalid = ! this . onceFlag && invalid && this . parent . ingoreDirty === false && ! this . ngControl ?. dirty ? false : invalid ; const errors = this . ngControl ?. errors ; if ( errors != null && Object . keys ( errors ) . length > 0 ) { const key = Object . keys ( errors ) [ 0 ] || '' ; const err = this . errorData [ key ] ; this . _error = err != null ? err : this . errorData [ '' ] || '' ; } this . cdr . detectChanges ( ) ; } checkContent ( ) { const el = this . contentElement . nativeElement ; const cls = `${ prefixCls }__item-empty` ; if ( isEmpty ( el ) ) { this . ren . addClass ( el , cls ) ; } else { this . ren . removeClass ( el , cls ) ; } } ngAfterContentInit ( ) { this . checkContent ( ) ; } ngOnChanges ( ) { this . onceFlag = this . parent . firstVisual ; if ( this . inited ) { this . setClass ( ) . bindModel ( ) ; } } ngAfterViewInit ( ) { this . setClass ( ) . bindModel ( ) ; this . inited = true ; if ( this . onceFlag ) { Promise . resolve ( ) . then ( ( ) => { this . updateStatus ( this . ngControl ?. invalid ! ) ; this . onceFlag = false ; } ) ; } } ngOnDestroy ( ) { const { unsubscribe$ } = this ; unsubscribe$ . next ( ) ; unsubscribe$ . complete ( ) ; } }