ohayolibs
Version:
Ohayo is a set of essential modules for ohayojp.
226 lines (202 loc) • 6.88 kB
text/typescript
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 { ResponsiveService } from '@ohayo/theme';
import { BooleanInput, InputBoolean, InputNumber, isEmpty, NumberInput } from '@ohayo/util';
import { helpMotion } from 'ng-zorro-antd/core/animation';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { SEContainerComponent } from './se-container.component';
import { SEError, SEErrorType } from './se.types';
const prefixCls = `se`;
let nextUniqueId = 0;
export class SEComponent implements OnChanges, AfterContentInit, AfterViewInit, OnDestroy {
static ngAcceptInputType_col: NumberInput;
static ngAcceptInputType_required: BooleanInput;
static ngAcceptInputType_line: BooleanInput;
static ngAcceptInputType_labelWidth: NumberInput;
private el: HTMLElement;
private unsubscribe$ = new Subject<void>();
private readonly ngModel: NgModel;
private readonly formControlName: FormControlName;
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;
_error: string | TemplateRef<void>;
// #region fields
optional: string | TemplateRef<void>;
optionalHelp: string | TemplateRef<void>;
optionalHelpColor: string;
set error(val: SEErrorType) {
this.errorData = typeof val === 'string' || val instanceof TemplateRef ? { '': val } : val;
}
extra: string | TemplateRef<void>;
label: string | TemplateRef<void>;
col: number;
required = false;
controlClass: string = '';
line: boolean;
labelWidth: number;
set id(value: string) {
this._id = value;
this._autoId = false;
}
_id = `_se-${++nextUniqueId}`;
_autoId = true;
// #endregion
get paddingValue(): number {
return this.parent.gutter / 2;
}
get showErr(): boolean {
return this.invalid && !!this._error && !this.compact;
}
get compact(): boolean {
return this.parent.size === 'compact';
}
private get ngControl(): NgModel | FormControlName {
return this.ngModel || this.formControlName;
}
constructor(
el: ElementRef,
private parent: SEContainerComponent,
private rep: ResponsiveService,
private ren: Renderer2,
private cdr: ChangeDetectorRef,
) {
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(): this {
const { el, ren, clsMap, col, parent, cdr, line, labelWidth, rep } = this;
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(): void {
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;
}
}
}
// auto required
if (this.required !== true) {
const rawValidators = (this.ngControl as NzSafeAny)?._rawValidators as Array<Validator>;
this.required = rawValidators.find(w => w instanceof RequiredValidator) != null;
this.cdr.detectChanges();
}
}
private updateStatus(invalid: boolean): void {
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(): void {
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(): void {
this.checkContent();
}
ngOnChanges(): void {
this.onceFlag = this.parent.firstVisual;
if (this.inited) {
this.setClass().bindModel();
}
}
ngAfterViewInit(): void {
this.setClass().bindModel();
this.inited = true;
if (this.onceFlag) {
Promise.resolve().then(() => {
this.updateStatus(this.ngControl.invalid!);
this.onceFlag = false;
});
}
}
ngOnDestroy(): void {
const { unsubscribe$ } = this;
unsubscribe$.next();
unsubscribe$.complete();
}
}