ng-devui
Version:
DevUI components based on Angular
1 lines • 106 kB
Source Map (JSON)
{"version":3,"file":"ng-devui-form.mjs","sources":["../../devui/form/form-control.component.ts","../../devui/form/form-control.component.html","../../devui/form/form-label.component.ts","../../devui/form/form-label.component.html","../../devui/form/form-item.component.ts","../../devui/form/validator-directive/validators.ts","../../devui/form/validator-directive/validate.type.ts","../../devui/form/validator-directive/form-control-rules.directive.ts","../../devui/form/form.directive.ts","../../devui/form/form-operation.component.ts","../../devui/form/services/d-validate-sync.service.ts","../../devui/form/validator-directive/d-validate-sync.directive.ts","../../devui/form/form.module.ts","../../devui/form/ng-devui-form.ts"],"sourcesContent":["import { ChangeDetectorRef, Component, ElementRef, HostBinding, Input, OnInit, Renderer2, TemplateRef } from '@angular/core';\nimport { DFormControlStatus } from './validator-directive/validate.type';\n\n@Component({\n selector: 'd-form-control',\n templateUrl: './form-control.component.html',\n styleUrls: ['./form-control.component.scss'],\n preserveWhitespaces: false,\n})\nexport class FormControlComponent implements OnInit {\n @Input() extraInfo: string | TemplateRef<any>;\n get extraInfoTemplate() {\n return this.extraInfo instanceof TemplateRef ? this.extraInfo : null;\n }\n\n errorMessage: string;\n @Input() feedbackStatus: DFormControlStatus | null;\n\n @Input() suffixTemplate: TemplateRef<any>;\n\n @HostBinding('class.devui-form-control-has-feedback') get status() {\n return !!this.feedbackStatus;\n }\n\n @HostBinding('class.devui-form-control-has-suffix') get suffix() {\n return !!this.suffixTemplate;\n }\n\n /**\n * @desc Compatible with z-index of components that include dropdown menu, will be remove.\n *\n */\n @HostBinding('class.devui-control-has-open-dropdown') get dropdownOpen() {\n return !!this.elementRef.nativeElement.querySelector('.devui-dropdown-origin-open');\n }\n\n constructor(private elementRef: ElementRef, renderer: Renderer2, private cdr: ChangeDetectorRef) {\n renderer.addClass(elementRef.nativeElement, 'devui-form-controls');\n }\n\n ngOnInit() {}\n\n public updateErrorMessage(message: string) {\n this.errorMessage = message;\n this.cdr.detectChanges();\n }\n\n public updateFeedbackStatus(status: DFormControlStatus | null): void {\n this.feedbackStatus = status;\n this.cdr.detectChanges();\n }\n}\n","<div class=\"devui-form-control-container\">\n <ng-content></ng-content>\n <span *ngIf=\"suffixTemplate\" class=\"devui-form-item-suffix suffix\">\n <ng-template [ngTemplateOutlet]=\"suffixTemplate\"></ng-template>\n </span>\n\n <span [attr.class]=\"'devui-form-item-icon devui-form-item-icon-' + feedbackStatus\" *ngIf=\"feedbackStatus\">\n <svg *ngIf=\"feedbackStatus === 'error'\" width=\"14px\" height=\"14px\" viewBox=\"0 0 16 16\">\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <circle cx=\"8\" cy=\"8\" r=\"8\"></circle>\n <polygon\n points=\"8.07106781 6.65685425 10.8994949 3.82842712 12.3137085 5.24264069 9.48528137 8.07106781 12.3137085 10.8994949 10.8994949 12.3137085 8.07106781 9.48528137 5.24264069 12.3137085 3.82842712 10.8994949 6.65685425 8.07106781 3.82842712 5.24264069 5.24264069 3.82842712\"\n ></polygon>\n </g>\n </svg>\n <svg *ngIf=\"feedbackStatus === 'success'\" width=\"14px\" height=\"14px\" viewBox=\"0 0 16 16\">\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <circle cx=\"8\" cy=\"8\" r=\"8\"></circle>\n <polygon\n points=\"6.53553391 9.77817459 12.1923882 4.12132034 13.6066017 5.53553391 6.53553391 12.6066017 3 9.07106781 4.41421356 7.65685425 6.53553391 9.77817459\"\n ></polygon>\n </g>\n </svg>\n <svg *ngIf=\"feedbackStatus === 'pending'\" width=\"14px\" height=\"14px\" viewBox=\"0 0 16 16\">\n <g id=\"loading\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <path\n d=\"M8,0 C12.4,0 16,3.6 16,8 C16,12.4 12.4,16 8,16 C3.6,16 0,12.4 0,8 C0,3.6 3.6,0 8,0 Z M8,1 C4.15,1 1,4.15 1,8 C1,11.85 4.15,15 8,15 C11.85,15 15,11.85 15,8 C15,4.15 11.85,1 8,1 Z\"\n fill-rule=\"nonzero\"\n ></path>\n <path d=\"M8,0 C12.4,0 16,3.6 16,8 L15,8 C15,4.15 11.85,1 8,1 L8,0 Z\" fill-rule=\"nonzero\"></path>\n </g>\n </svg>\n </span>\n</div>\n<div class=\"devui-form-control-inner-content\">\n <div class=\"devui-form-message\" *ngIf=\"errorMessage\">\n <span class=\"devui-form-message-text\">\n <span>\n {{ errorMessage }}\n </span>\n </span>\n </div>\n <div *ngIf=\"extraInfo\" class=\"devui-form-control-extra-info\">\n <ng-template [ngTemplateOutlet]=\"extraInfoTemplate || defaultTemplate\"> </ng-template>\n <ng-template #defaultTemplate>\n {{ extraInfo }}\n </ng-template>\n </div>\n</div>\n","import {\n ChangeDetectorRef,\n Component,\n ElementRef,\n HostBinding,\n Input, Renderer2, TemplateRef\n} from '@angular/core';\nimport { DFormControlStatus } from './validator-directive/validate.type';\n\n@Component({\n selector: 'd-form-label',\n templateUrl: './form-label.component.html',\n styleUrls: ['./form-label.component.scss'],\n preserveWhitespaces: false\n})\nexport class FormLabelComponent {\n @Input() required = false;\n @Input() hasHelp = false;\n @Input() helpTips = '';\n @Input() customHelpTipTemplate: TemplateRef<any>;\n\n feedbackStatus: DFormControlStatus | null = null;\n\n @HostBinding('class.devui-form-label-has-feedback') get status() {\n return this.feedbackStatus !== null;\n }\n\n constructor(\n private cdr: ChangeDetectorRef,\n elementRef: ElementRef,\n renderer: Renderer2\n ) {\n renderer.addClass(elementRef.nativeElement, 'devui-form-label');\n }\n\n public updateFeedbackStatus(status: DFormControlStatus | null): void {\n this.feedbackStatus = status;\n this.cdr.detectChanges();\n }\n}\n","<span class=\"devui-form-span\" [ngClass]=\"{ 'devui-required': required }\">\n <ng-content></ng-content>\n</span>\n<svg\n *ngIf=\"hasHelp\"\n dPopover\n [content]=\"customHelpTipTemplate || helpTipsTemplate\"\n [popType]=\"'info'\"\n [position]=\"'top'\"\n [controlled]=\"true\"\n trigger=\"hover\"\n class=\"devui-helping\"\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 16 16\"\n>\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <path\n d=\"M8,1 C9.88227821,1 11.6450792,1.74560326 12.9497351,3.05025198 C14.2543924,4.35492369 15,6.11772556 15,8 C15,9.88227587 14.2543913,11.6450767 12.949734,12.949734 C11.6450767,14.2543913 9.88227587,15 8,15 C6.11772556,15 4.35492369,14.2543924 3.05024866,12.9497318 C1.74560326,11.6450792 1,9.88227821 1,8 C1,6.11772322 1.74560213,4.35492125 3.0502542,3.05024755 C4.35492125,1.74560213 6.11772322,1 8,1 Z M8,11.2634757 C7.47757297,11.2634757 7.05405405,11.6869946 7.05405405,12.2094216 C7.05405405,12.7318486 7.47757297,13.1553676 8,13.1553676 C8.52242703,13.1553676 8.94594595,12.7318486 8.94594595,12.2094216 C8.94594595,11.6869946 8.52242703,11.2634757 8,11.2634757 Z M8,3.12834054 C6.43270866,3.12834054 5.16216216,4.39888703 5.16216216,5.96617838 C5.16216216,6.27963729 5.41627082,6.53374595 5.72972973,6.53374595 C6.04318864,6.53374595 6.2972973,6.27963729 6.2972973,5.96617838 C6.2972973,5.02580486 7.05962648,4.26347568 8,4.26347568 C8.94037352,4.26347568 9.7027027,5.02580486 9.7027027,5.96617838 C9.7027027,6.9065519 8.94037352,7.66888108 8,7.66888108 C7.68654109,7.66888108 7.43243243,7.92298974 7.43243243,8.23644865 L7.43243243,8.23644865 L7.43243243,9.74996216 L7.44157671,9.85198315 C7.48963374,10.1167525 7.72136986,10.3175297 8,10.3175297 L8,10.3175297 L8.10202099,10.3083855 C8.36679036,10.2603284 8.56756757,10.0285923 8.56756757,9.74996216 L8.56756757,9.74996216 L8.56756757,8.74578378 L8.72552115,8.71042694 C9.94134023,8.38984467 10.8378378,7.28270311 10.8378378,5.96617838 C10.8378378,4.39888703 9.56729134,3.12834054 8,3.12834054 Z\"\n fill-rule=\"nonzero\"\n ></path>\n </g>\n</svg>\n<svg *ngIf=\"feedbackStatus === 'error'\" class=\"devui-icon devui-icon-error\" width=\"16px\" height=\"16px\" viewBox=\"0 0 16 16\">\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <circle cx=\"8\" cy=\"8\" r=\"7\"></circle>\n <path\n d=\"M8,0 C3.6,0 0,3.6 0,8 C0,12.4 3.6,16 8,16 C12.4,16 16,12.4 16,8 C16,3.6 12.4,0 8,0 Z M9,12.6 L7,12.6 L7,10.6 L9,10.6 L9,12.6 Z M9,9.1 L7,9.1 L6.9,3.1 L9.1,3.1 L9,9.1 Z\"\n fill-rule=\"nonzero\"\n ></path>\n </g>\n</svg>\n<ng-template #helpTipsTemplate>\n <span class=\"devui-help-tips\">{{ helpTips }}</span>\n</ng-template>\n","import { ChangeDetectorRef, Component, ContentChild, ElementRef, HostBinding, Input, OnInit, Renderer2 } from '@angular/core';\nimport { FormControlComponent } from './form-control.component';\nimport { FormLabelComponent } from './form-label.component';\nimport { DFormControlStatus } from './validator-directive/validate.type';\n\n@Component({\n selector: 'd-form-item',\n template: ` <ng-content></ng-content> `,\n styleUrls: ['./form-item.component.scss'],\n preserveWhitespaces: false,\n})\nexport class FormItemComponent implements OnInit {\n @HostBinding('class.devui-form-has-error-msg') _hasErrorMsg = false;\n\n /**\n * @deprecated Use dHasFeedback to replace, No longer support for label\n */\n @Input() dFeedbackType: 'label' | 'control';\n\n @Input() dHasFeedback = false;\n\n @ContentChild(FormControlComponent) controlInstance: FormControlComponent;\n @ContentChild(FormLabelComponent) labelInstance: FormLabelComponent;\n\n constructor(elementRef: ElementRef, renderer: Renderer2, private cdr: ChangeDetectorRef) {\n // FIXME: 循环依赖,打包后将不可用:FormItemComponent -> FormDirective -> DFormControlRuleDirective -> FormItemComponent\n // @Optional() @Host() @SkipSelf() private _dForm: FormDirective,\n\n renderer.addClass(elementRef.nativeElement, 'devui-form-item');\n }\n\n ngOnInit() {}\n\n updateFeedback(status: DFormControlStatus | null, updateMessage: string): void {\n this._hasErrorMsg = !!updateMessage;\n\n const feedbackType = this.dFeedbackType;\n if (feedbackType === 'label' && this.labelInstance) {\n this.labelInstance.updateFeedbackStatus(status);\n } else if ((feedbackType === 'control' || this.dHasFeedback) && this.controlInstance) {\n this.controlInstance.updateFeedbackStatus(status);\n }\n\n if (this.controlInstance) {\n this.controlInstance.updateErrorMessage(updateMessage);\n }\n\n this.cdr.detectChanges();\n }\n}\n","import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';\n\n// @dynamic\nexport class DValidators {\n // eslint-disable-next-line max-len\n private static readonly ScriptPattern = /<+\\/?[Ss][Cc][Rr][Ii][Pp][Tt] *.*>*/;\n // eslint-disable-next-line max-len\n private static readonly UrlPattern = /^(https?|s?ftp):\\/\\/(((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:)*@)?(((((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))\\.){3}((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5])))|((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?)(:\\d*)?)(\\/((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)+(\\/(([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)*)*)?)?(\\?((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)|[\\uE000-\\uF8FF]|\\/|\\?)*)?(#((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)|\\/|\\?)*)?$/i;\n private static readonly Ipv4Pattern = /^(((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))\\.){3}((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))$/i;\n // eslint-disable-next-line max-len\n private static readonly Ipv6Pattern = /^(((([\\da-f]{1,4}):){7}([\\da-f]{1,4}))|(((([\\da-f]{1,4}):){1,7}:)|((([\\da-f]{1,4}):){6}:([\\da-f]{1,4}))|((([\\da-f]{1,4}):){5}:(([\\da-f]{1,4}):)?([\\da-f]{1,4}))|((([\\da-f]{1,4}):){4}:(([\\da-f]{1,4}):){0,2}([\\da-f]{1,4}))|((([\\da-f]{1,4}):){3}:(([\\da-f]{1,4}):){0,3}([\\da-f]{1,4}))|((([\\da-f]{1,4}):){2}:(([\\da-f]{1,4}):){0,4}([\\da-f]{1,4}))|((([\\da-f]{1,4}):){1}:(([\\da-f]{1,4}):){0,5}([\\da-f]{1,4}))|(::(([\\da-f]{1,4}):){0,6}([\\da-f]{1,4}))|(::([\\da-f]{1,4})?))|(((([\\da-f]{1,4}):){6}(((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))\\.){3}((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5])))|((([\\da-f]{1,4}):){5}:(((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))\\.){3}((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5])))|((([\\da-f]{1,4}):){4}:(([\\da-f]{1,4}):)?(((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))\\.){3}((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5])))|((([\\da-f]{1,4}):){3}:(([\\da-f]{1,4}):){0,2}(((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))\\.){3}((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5])))|((([\\da-f]{1,4}):){2}:(([\\da-f]{1,4}):){0,3}(((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))\\.){3}((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5])))|(([\\da-f]{1,4})::(([\\da-f]{1,4}):){0,4}(((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))\\.){3}((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5])))|(::(([\\da-f]{1,4}):){0,5}(((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5]))\\.){3}((1?[1-9]?\\d)|(10\\d)|(2[0-4]\\d)|(25[0-5])))))$/i;\n private static readonly AlphabetPattern = /^[a-zA-Z]+(\\s+[a-zA-Z]+)*$/;\n private static readonly DigitPattern = /^\\d+$/;\n private static readonly IntegerPattern = /^-?\\d+$/;\n private static readonly NumberPattern = /^(?:-?\\d+|[+-]?[\\d]+([\\.][\\d]+)?([Ee][+-]?[\\d]+)?|-?\\d{1,3}(?:,\\d{3})+)?(?:\\.\\d+)?$/; // 支持科学计数法\n private static readonly PortRange: [number, number] = [0, 65535];\n private static isEmptyInput(value: any): boolean {\n return value === null || value.length === 0;\n }\n\n /* Failed if only has whitespace */\n public static whiteSpace(control: AbstractControl): ValidationErrors | null {\n let res = null;\n if (typeof control.value === 'string' && control.value.length !== 0 && control.value.trim().length === 0) {\n res = { whitespace: true };\n }\n return res;\n }\n\n /**\n * 校验是否包含\n * @param contain\n * @returns\n */\n public static contains(contain: string | number): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n if (DValidators.isEmptyInput(control.value) || DValidators.isEmptyInput(contain)) {\n return null;\n }\n\n return control.value.indexOf(contain) === -1 ? { contains: { requiredContains: contain, actualValue: control.value } } : null;\n };\n }\n\n /**\n * 校验是否不包含\n * @param contain\n * @returns\n */\n public static notContains(contain: string | number): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n if (DValidators.isEmptyInput(control.value) || DValidators.isEmptyInput(contain)) {\n return null;\n }\n\n return control.value.indexOf(contain) !== -1 ? { notContains: { requiredNotContains: contain, actualValue: control.value } } : null;\n };\n }\n\n /**\n * 校验是否相等\n * @param equal\n * @returns\n */\n public static equal(equal: string | number | boolean): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n if (DValidators.isEmptyInput(control.value) || DValidators.isEmptyInput(equal)) {\n return null;\n }\n\n return control.value !== equal ? { equal: { requiredEqual: equal, actualValue: control.value } } : null;\n };\n }\n\n /**\n * 校验是否不相等\n * @param equal\n * @returns\n */\n public static notEqual(equal: string | number): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n if (DValidators.isEmptyInput(control.value) || DValidators.isEmptyInput(equal)) {\n return null;\n }\n\n return control.value === equal ? { notEqual: { requiredNotEqual: equal, actualValue: control.value } } : null;\n };\n }\n\n /**\n * 校验端口号\n * @param control\n * @returns\n */\n public static port(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null;\n }\n\n return DValidators.DigitPattern.test(control.value) &&\n control.value >= DValidators.PortRange[0] &&\n control.value <= DValidators.PortRange[1]\n ? null\n : { port: { min: DValidators.PortRange[0], max: DValidators.PortRange[1] } };\n }\n\n /**\n * 校验日期是否合法\n * @param control\n * @returns\n */\n public static date(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null; // don't validate empty values to allow optional controls\n }\n\n return !/Invalid|NaN/.test(new Date(control.value).toString()) ? null : { date: true };\n }\n\n /**\n * 校验 url 是否合法\n * @param control\n * @returns\n */\n public static url(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null; // don't validate empty values to allow optional controls\n }\n\n return DValidators.UrlPattern.test(control.value) ? null : { url: true };\n }\n\n /**\n * 校验整数\n * @param control\n * @returns\n */\n public static integer(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null; // don't validate empty values to allow optional controls\n }\n\n return DValidators.IntegerPattern.test(control.value) ? null : { integer: true };\n }\n\n /**\n * 校验是否是数字\n * @param control\n * @returns\n */\n public static digits(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null; // don't validate empty values to allow optional controls\n }\n\n return DValidators.DigitPattern.test(control.value) ? null : { digits: true };\n }\n\n /**\n * 校验数字,支持科学计数法\n * @param control\n * @returns\n */\n public static number(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null; // don't validate empty values to allow optional controls\n }\n\n return DValidators.NumberPattern.test(control.value) ? null : { number: true };\n }\n\n /**\n * 校验是否是字母\n * @param control\n * @returns\n */\n public static alphabet(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null;\n }\n\n return DValidators.AlphabetPattern.test(control.value) ? null : { alphabet: true };\n }\n\n /**\n * 校验是 script 标签\n * @param control\n * @returns\n */\n public static notScript(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null;\n }\n\n return DValidators.ScriptPattern.test(control.value) ? { notScript: true } : null;\n }\n\n /**\n * 校验 ipv4\n * @param control\n * @returns\n */\n public static ipv4(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null;\n }\n\n return DValidators.Ipv4Pattern.test(control.value) ? null : { ipv4: true };\n }\n\n /**\n * 校验 ipv6\n * @param control\n * @returns\n */\n public static ipv6(control: AbstractControl): ValidationErrors | null {\n if (DValidators.isEmptyInput(control.value)) {\n return null;\n }\n\n return DValidators.Ipv6Pattern.test(control.value) ? null : { ipv6: true };\n }\n}\n","import { TemplateRef } from '@angular/core';\nimport { AsyncValidatorFn, ValidatorFn, Validators } from '@angular/forms';\nimport { Observable } from 'rxjs';\nimport { DValidators } from './validators';\n\n/* pristine: 抛出error包括pristine状态\n ** dirty: 抛出error需在dirty状态\n */\nexport type DValidationErrorStrategy = 'pristine' | 'dirty';\n\nexport type DFormControlStatus = 'error' | 'pending' | 'success';\n\n// DForm ValidatorFn\nexport type DValidatorFn = (value: any) => boolean | string | { [key: string]: string } | null;\n\n// DForm AsyncValidatorFn\nexport type DAsyncValidatorFn = (value: any) => Observable<boolean | string | { [key: string]: string } | null>;\n\n/* id: 自定义验证器必须具有(也可不必)\n ** validator: 自定义验证器必须传入,可支持函数与正则类型,函数必须返回true或false\n ** 其他key:必须是已注册key\n ** priority: error抛出的优先级,同时错误,值越大越优先抛出,值相同,后注册先抛出\n ** COMMENT: 我们这里将提供warning状态,warning状态不会向上传递,不会加入实际校验器中,在所有校验器校验完成后进行校验,若有错则不再进行校验,若无错,则进行校验。\n ** warning若为异步校验器,只更新当前devui-pending状态,不更新全局\n */\nexport interface DValidateRule {\n id?: string;\n validator?: DValidatorFn | ValidatorFn;\n message?: string | { [key: string]: string } | TemplateRef<any>;\n errorStrategy?: DValidationErrorStrategy;\n priority?: number;\n isNgValidator?: boolean;\n validateLevel?: 'error' | 'warning'; // 校验级别\n [id: string]: boolean | number | string |\n { [key: string]: string } | RegExp | DValidatorFn | ValidatorFn | undefined | TemplateRef<any>; // 万能key\n}\nexport interface DAsyncValidateRule {\n id?: string;\n validator?: DAsyncValidatorFn | AsyncValidatorFn;\n message?: string | { [key: string]: string } | TemplateRef<any>;\n errorStrategy?: DValidationErrorStrategy;\n priority?: number;\n isNgValidator?: boolean;\n validateLevel?: 'error' | 'warning'; // 校验级别\n [id: string]:\n | boolean\n | number\n | string\n | { [key: string]: string }\n | RegExp\n | DAsyncValidatorFn\n | AsyncValidatorFn\n | undefined\n | TemplateRef<any>; // 万能key\n}\n\n// TODO: 还需提供一个debounceTime\nexport type DValidateRules =\n | {\n validators?: DValidateRule[];\n asyncValidators?: DAsyncValidateRule[];\n asyncDebounceTime?: number; // 异步校验器debounceTime\n errorStrategy?: DValidationErrorStrategy; // error更新策略\n message?: string | { [key: string]: string } | TemplateRef<any>; // 统一配置的message\n updateOn?: 'change' | 'blur' | 'submit'; // model更新策略\n messageShowType?: 'popover' | 'text' | 'none'; // 消息自动显示策略(当前仅单个表单组件下生效),自身附着popover | form-control下显示 | 不显示,\n popPosition?:\n | 'left'\n | 'right'\n | 'top'\n | 'bottom'\n | 'bottom-left'\n | 'bottom-right'\n | 'top-left'\n | 'top-right'\n | 'left-top'\n | 'left-bottom'\n | 'right-top'\n | 'right-bottom'\n | (\n | 'left'\n | 'right'\n | 'top'\n | 'bottom'\n | 'bottom-left'\n | 'bottom-right'\n | 'top-left'\n | 'top-right'\n | 'left-top'\n | 'left-bottom'\n | 'right-top'\n | 'right-bottom'\n )[];\n }\n | DValidateRule[];\n\nexport interface DValidateErrorStatus {\n errorMessage: string | { [key: string]: string } | null;\n showError: boolean;\n errors: { [key: string]: any };\n}\n\n/* TODO: 这里是否需要导出 */\nexport const ruleReservedWords = [\n 'id',\n 'validator',\n 'message',\n 'errorStrategy',\n 'priority',\n 'isNgValidator',\n 'popPosition',\n 'asyncDebounceTime',\n];\n\n// 这里要考虑如果可全局添加默认\nexport const dDefaultValidators = {\n required: Validators.required,\n minlength: Validators.minLength, // 实际设置时再次调用该方法传入具体值\n maxlength: Validators.maxLength, // 实际设置时再次调用该方法传入具体值\n min: Validators.min,\n max: Validators.max,\n requiredTrue: Validators.requiredTrue,\n email: Validators.email,\n pattern: Validators.pattern,\n whitespace: DValidators.whiteSpace,\n};\n\nexport interface DPopConfig {\n popMaxWidth?: number;\n scrollElement?: Element;\n zIndex?: number;\n showAnimation?: boolean;\n}\n","import {\r\n ComponentFactoryResolver,\r\n ComponentRef,\r\n Directive,\r\n ElementRef,\r\n EventEmitter,\r\n Host,\r\n Input,\r\n OnChanges,\r\n OnDestroy,\r\n OnInit,\r\n Optional,\r\n Output,\r\n Self,\r\n SimpleChanges,\r\n SkipSelf,\r\n TemplateRef,\r\n} from '@angular/core';\r\nimport {\r\n AbstractControl,\r\n AbstractControlDirective,\r\n AsyncValidatorFn,\r\n ControlContainer,\r\n NgControl,\r\n ValidationErrors,\r\n ValidatorFn,\r\n} from '@angular/forms';\r\nimport { I18nInterface, I18nService } from 'ng-devui/i18n';\r\nimport { OverlayContainerRef } from 'ng-devui/overlay-container';\r\nimport { PopoverComponent } from 'ng-devui/popover';\r\nimport { Observable, Subject, fromEvent, merge, timer } from 'rxjs';\r\nimport { map, switchMap, takeUntil } from 'rxjs/operators';\r\nimport { FormItemComponent } from '../form-item.component';\r\nimport {\r\n DAsyncValidateRule,\r\n DFormControlStatus,\r\n DPopConfig,\r\n DValidateErrorStatus,\r\n DValidateRule,\r\n DValidateRules,\r\n DValidationErrorStrategy,\r\n dDefaultValidators,\r\n ruleReservedWords,\r\n} from './validate.type';\r\n\r\n@Directive()\r\nexport abstract class DAbstractControlRuleDirective implements OnChanges {\r\n /* mode is _cd.control */\r\n public readonly _cd: AbstractControlDirective; // model is _cd.control\r\n\r\n /* parent dValidateRuleDirective */\r\n private _parent: DAbstractControlRuleDirective;\r\n\r\n /* 预置rules: originRules */\r\n private _originRules: DValidateRules;\r\n\r\n /* rules */\r\n private _rules: DValidateRules;\r\n\r\n /* 统一设置错误抛出策略 */\r\n private _errorStrategy: DValidationErrorStrategy = 'dirty'; // 统一设置错误抛出策略\r\n\r\n /* rules map */\r\n private _messageOpts: { [key: string]: DValidateRule | DAsyncValidateRule };\r\n\r\n /* 是否已经注册监听 */\r\n private _registered = false;\r\n\r\n public readonly errors: { [key: string]: any } | null;\r\n\r\n private _errorMessage: string = null;\r\n\r\n /* status warning */\r\n private _warning: boolean;\r\n\r\n /* 内置国际化text */\r\n i18nFormText: I18nInterface['form'];\r\n\r\n /* language key */\r\n _locale: string;\r\n set locale(key: string) {\r\n this._locale = key;\r\n this._parseErrors(this._cd.control.errors);\r\n this.updateStatusAndMessageToView(this._cd.control.status);\r\n }\r\n\r\n get locale(): string {\r\n return this._locale;\r\n }\r\n\r\n public get errorMessage() {\r\n return this._cd && this._cd.control.invalid\r\n ? this._errorMessage || (this._rules && (this._rules as { message: string }).message)\r\n : null;\r\n }\r\n\r\n public set errorMessage(msg: string) {\r\n if (this._cd && this._cd.control.invalid) {\r\n this._errorMessage = msg;\r\n } else {\r\n this._errorMessage = null;\r\n }\r\n }\r\n\r\n @Output() dRulesStatusChange = new EventEmitter<DValidateErrorStatus>();\r\n\r\n constructor(cd: AbstractControlDirective, parent: DAbstractControlRuleDirective) {\r\n this._cd = cd;\r\n this._parent = parent;\r\n }\r\n\r\n get isReady() {\r\n return this._cd.control ? !(this._cd.control.invalid || this._cd.control.pending) : true;\r\n }\r\n\r\n get pending() {\r\n return this._cd.control ? this._cd.control.pending : true;\r\n }\r\n\r\n /* 包含继承自父级的rule */\r\n get fullRules(): DValidateRules {\r\n const keysCanInherit = ['messageShowType', 'errorStrategy', 'messageToView', 'popPosition', 'asyncDebounceTime'];\r\n const resRules = { ...this._rules };\r\n keysCanInherit.forEach((key) => {\r\n if (this._parent && this._parent.fullRules) {\r\n resRules[key] = resRules[key] !== undefined ? resRules[key] : this._parent.fullRules[key];\r\n }\r\n });\r\n return resRules;\r\n }\r\n\r\n get asyncValidatorDebounceTime(): number {\r\n const time = (this.fullRules as { asyncDebounceTime: number }).asyncDebounceTime;\r\n return time === undefined ? 300 : time;\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges): void {\r\n if ('rules' in changes && !this._rules) {\r\n // TODO:提供外部调用可手动更新rule方法\r\n this._rules = { ...this._originRules, ...this._translateRulesToObject(changes.rules.currentValue) };\r\n this.setupOrUpdateRules();\r\n }\r\n\r\n if (!this._registered) {\r\n this._registerOnStatusChange();\r\n }\r\n }\r\n\r\n public updateRules(rules: DValidateRules): void {\r\n this._rules = { ...this._originRules, ...this._translateRulesToObject(rules) };\r\n this.setupOrUpdateRules();\r\n }\r\n\r\n public setOriginRules(rules: DValidateRules): void {\r\n this._originRules = this._translateRulesToObject(rules);\r\n }\r\n\r\n public setupOrUpdateRules(): void {\r\n // TODO:校验rules规则是否合法\r\n this._transformRulesAndUpdateToModel();\r\n this._setUpdateStrategy();\r\n }\r\n private _transformRulesAndUpdateToModel(): void {\r\n this._messageOpts = {};\r\n\r\n if (!Array.isArray(this._rules)) {\r\n if (this._rules.validators) {\r\n const validators: ValidatorFn[] = this._transformValidatorsToFnArray(this._rules.validators) as ValidatorFn[];\r\n this._updateValidators(validators);\r\n }\r\n if (this._rules.asyncValidators) {\r\n const asyncValidators: AsyncValidatorFn[] = this._transformValidatorsToFnArray(\r\n this._rules.asyncValidators,\r\n true\r\n ) as AsyncValidatorFn[];\r\n this._updateAsyncValidators(asyncValidators);\r\n }\r\n } else {\r\n const validators: ValidatorFn[] = this._transformValidatorsToFnArray(this._rules) as ValidatorFn[];\r\n this._updateValidators(validators);\r\n }\r\n this._updateValueAndValidity();\r\n }\r\n\r\n private _transformValidatorsToFnArray(\r\n validators: DValidateRule[] | DAsyncValidateRule[],\r\n async = false\r\n ): ValidatorFn[] | AsyncValidatorFn[] {\r\n const resultFns = [];\r\n validators.forEach((validatorRule: DValidateRule) => {\r\n // TODO: 提供可全局统一注册方法\r\n const validatorId: string = this._autoGetIdFromRule(validatorRule);\r\n let validator = null;\r\n\r\n if (!validatorId) {\r\n // TODO:抛出错误\r\n }\r\n\r\n if (validatorId in dDefaultValidators) {\r\n validator = this._generateValidatorFnFromDefault(validatorId, validatorRule[validatorId]);\r\n } else {\r\n if (typeof validatorRule[validatorId] === 'string') {\r\n validator = validatorRule.validator;\r\n } else {\r\n validator = validatorRule[validatorId];\r\n }\r\n if (!validatorRule.isNgValidator) {\r\n if (!async) {\r\n validator = this._transformRuleToNgValidator(validatorId, validator, validatorRule.message);\r\n } else {\r\n validator = this._transformRuleToNgAsyncValidator(validatorId, validator, validatorRule.message);\r\n }\r\n }\r\n }\r\n\r\n if (validator) {\r\n if (async && this.asyncValidatorDebounceTime) {\r\n const oldValidator = validator;\r\n validator = (control: AbstractControl): Observable<ValidationErrors | null> => {\r\n return timer(this.asyncValidatorDebounceTime).pipe(\r\n switchMap(() => {\r\n return oldValidator(control);\r\n })\r\n );\r\n };\r\n }\r\n resultFns.push(validator);\r\n this._messageOpts[validatorId] = validatorRule;\r\n }\r\n // else {\r\n // // TODO: 抛出错误\r\n // }\r\n });\r\n return resultFns as AsyncValidatorFn[] | ValidatorFn[];\r\n }\r\n\r\n private _translateRulesToObject(rules: DValidateRules) {\r\n if (Array.isArray(rules)) {\r\n return {\r\n validators: rules,\r\n };\r\n }\r\n\r\n return rules;\r\n }\r\n\r\n private _findNgValidatorInDefault(validatorRule: DValidateRule) {\r\n for (const key in dDefaultValidators) {\r\n if (Object.prototype.hasOwnProperty.call(validatorRule, key)) {\r\n return { id: key, ngValidator: this._generateValidatorFnFromDefault(key, validatorRule[key]) };\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n private _generateValidatorFnFromDefault(key, value) {\r\n if (typeof value === 'boolean' && value) {\r\n // boolean无需再执行函数进行传值\r\n return dDefaultValidators[key];\r\n } else if (typeof value !== 'boolean') {\r\n return dDefaultValidators[key](value);\r\n }\r\n return null;\r\n }\r\n\r\n private _transformRuleToNgValidator(id, validatorFn, message) {\r\n return (control: AbstractControl): ValidationErrors | null => {\r\n const res = validatorFn(control.value);\r\n return this._transValidatorResultToNgError(id, res, message);\r\n };\r\n }\r\n\r\n private _transformRuleToNgAsyncValidator(id, validator, message) {\r\n return (control: AbstractControl): Observable<ValidationErrors | null> => {\r\n return (validator(control.value) as Observable<boolean>).pipe(\r\n map((res) => {\r\n return this._transValidatorResultToNgError(id, res, message);\r\n })\r\n );\r\n };\r\n }\r\n\r\n private _transValidatorResultToNgError(id: string, res: boolean | string | null, message: string) {\r\n let error = null;\r\n if (typeof res === 'boolean' && !res) {\r\n error = {};\r\n error[id] = message;\r\n } else if (typeof res === 'string' || (res && typeof res === 'object')) {\r\n // 兼容国际化词条\r\n error = {};\r\n error[id] = res;\r\n }\r\n return error;\r\n }\r\n\r\n private _autoGetIdFromRule(rule: DValidateRule | DAsyncValidateRule) {\r\n for (const key in rule) {\r\n if (!(key in ruleReservedWords)) {\r\n return key;\r\n }\r\n }\r\n return rule.id || null;\r\n }\r\n\r\n // TODO: 考虑自定义函数返回多种key场景\r\n get dClassError() {\r\n if (this._errorStrategy === 'dirty') {\r\n return this._cd.control ? this._cd.control.invalid && this._cd.control.dirty : false;\r\n } else {\r\n return this._cd.control ? this._cd.control.invalid : false;\r\n }\r\n }\r\n\r\n get showError() {\r\n return this.dClassError;\r\n }\r\n\r\n get showStatus() {\r\n if (this._errorStrategy === 'dirty') {\r\n return this._cd.control ? this._cd.control.dirty : false;\r\n } else {\r\n return true;\r\n }\r\n }\r\n\r\n get dClassSuccess() {\r\n // COMMENT: 暂不默认提供\r\n if ((this._rules as any).errorStrategy === 'dirty') {\r\n return this._cd.control ? this._cd.control.valid && this._cd.control.dirty : false;\r\n } else if (!(this._rules as any).errorStrategy) {\r\n return false;\r\n } else {\r\n return this._cd.control ? this._cd.control.valid : false;\r\n }\r\n }\r\n\r\n get dClassWarning() {\r\n return this._warning ? true : false;\r\n }\r\n\r\n get invalid() {\r\n return this._cd.control ? this._cd.control.invalid : false;\r\n }\r\n\r\n private _registerOnStatusChange() {\r\n if (this._cd && this._cd.control) {\r\n this._cd.control.statusChanges.subscribe((status) => {\r\n this._parseErrors(this._cd.control.errors);\r\n this._updateParent(); // update error message to parent directive\r\n this.updateStatusAndMessageToView(status);\r\n });\r\n this._registered = true;\r\n }\r\n }\r\n\r\n private _parseErrors(errors: { [key: string]: any }): void {\r\n if (!errors) {\r\n this._errorMessage = null;\r\n } else {\r\n /* if a rule did not have a message, we will try to get a message from errors by id */\r\n const { resId, resRule } = this._getARuleByErrors(errors);\r\n this._errorStrategy = this._getErrorStrategy(resRule);\r\n this._errorMessage =\r\n resRule &&\r\n (resRule.message ||\r\n this._getMessageFormErrorsById(errors, resId) ||\r\n this._getDefaultErrorMessage(resRule, resId) ||\r\n (this._rules as { message: string }).message);\r\n }\r\n\r\n this.dRulesStatusChange.emit({\r\n showError: this.showError,\r\n errorMessage: this._errorMessage,\r\n errors: errors,\r\n });\r\n }\r\n\r\n private _getDefaultErrorMessage(rule, id) {\r\n return rule && rule[id] && this.i18nFormText[id] && this.i18nFormText[id](rule[id]);\r\n }\r\n\r\n private _getErrorStrategy(rule?) {\r\n return (rule && rule.errorStrategy) || (this._rules as any).errorStrategy || 'dirty';\r\n }\r\n\r\n private _getMessageFormErrorsById(errors: { [key: string]: any }, id: string): string | null {\r\n if (errors[id] && typeof errors[id] === 'string') {\r\n return errors[id];\r\n } else if (errors[id] && typeof errors[id] === 'object' && (errors[id][this.locale] || errors[id].default)) {\r\n return errors[id];\r\n } else {\r\n return null;\r\n }\r\n }\r\n\r\n private _getARuleByErrors(errors: { [key: string]: any }) {\r\n // TODO:处理errors为null\r\n let resId: string;\r\n let resRule = null;\r\n for (const key of Object.keys(errors)) {\r\n if (this._messageOpts[key]) {\r\n if (resRule) {\r\n const priority = resRule.priority || 0;\r\n if (this._messageOpts[key].priority && this._messageOpts[key].priority > priority) {\r\n resId = key;\r\n resRule = this._messageOpts[key];\r\n }\r\n } else {\r\n resId = key;\r\n resRule = this._messageOpts[key];\r\n }\r\n }\r\n }\r\n return { resId: resId, resRule: resRule };\r\n }\r\n\r\n _updateParent() {\r\n if (this._parent) {\r\n // TODO\r\n }\r\n }\r\n\r\n private _setUpdateStrategy(): void {\r\n if (!Array.isArray(this._rules) && typeof this._rules === 'object' && this._rules.updateOn) {\r\n (this._cd.control as any)._updateOn = this._rules.updateOn;\r\n }\r\n }\r\n\r\n public _updateValueAndValidity() {\r\n if (this._cd && this._cd.control) {\r\n this._cd.control.updateValueAndValidity();\r\n }\r\n }\r\n\r\n private _updateValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void {\r\n if (this._cd && this._cd.control) {\r\n this._cd.control.setValidators(newValidator);\r\n }\r\n }\r\n\r\n private _updateAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[] | null): void {\r\n if (this._cd && this._cd.control) {\r\n this._cd.control.setAsyncValidators(newValidator);\r\n }\r\n }\r\n\r\n abstract updateStatusAndMessageToView(status: any): void;\r\n}\r\n\r\nconst dControlErrorStatusHost = {\r\n '[class.devui-error]': 'dClassError',\r\n '[class.devui-success]': 'dClassSuccess',\r\n '[class.devui-warning]': 'dClassWarning',\r\n};\r\n\r\n@Directive({\r\n selector: `[dValidateRules][formGroupName],[dValidateRules][formArrayName],[dValidateRules][ngModelGroup],\r\n [dValidateRules][formGroup],[dValidateRules]form:not([ngNoForm]),[dValidateRules][ngForm]`,\r\n /* eslint-disable-next-line @angular-eslint/no-host-metadata-property*/\r\n host: dControlErrorStatusHost,\r\n exportAs: 'dValidateRules',\r\n})\r\nexport class DFormGroupRuleDirective extends DAbstractControlRuleDirective implements OnInit, OnChanges, OnDestroy {\r\n @Input('dValidateRules') rules: DValidateRules;\r\n @Output() dRulesStatusChange: EventEmitter<any> = new EventEmitter<any>();\r\n\r\n private destroy$ = new Subject<void>();\r\n\r\n constructor(@Self() cd: ControlContainer, @Optional() @Host() @SkipSelf() parentDir: DFormGroupRuleDirective, private i18n: I18nService) {\r\n super(cd, parentDir);\r\n }\r\n\r\n ngOnInit(): void {\r\n this.i18nFormText = this.i18n.getI18nText().form;\r\n this.locale = this.i18n.getI18nText().locale;\r\n\r\n this.i18n\r\n .langChange()\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe((data: I18nInterface) => {\r\n this.i18nFormText = data.form;\r\n this.locale = data.locale;\r\n });\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n setErrorMessageByChild(msg: string) {\r\n if (!this.errorMessage) {\r\n this.errorMessage = msg;\r\n }\r\n }\r\n\r\n updateStatusAndMessageToView(status: any): void {\r\n // do nothing\r\n }\r\n}\r\n\r\n@Directive({\r\n selector: '[dValidateRules][formControlName],[dValidateRules][ngModel],[dValidateRules][formControl]',\r\n /* eslint-disable-next-line @angular-eslint/no-host-metadata-property*/\r\n host: dControlErrorStatusHost,\r\n exportAs: 'dValidateRules',\r\n})\r\nexport class DFormControlRuleDirective extends DAbstractControlRuleDirective implements OnInit, OnChanges, OnDestroy {\r\n @Input('dValidateRules') rules: DValidateRules;\r\n @Output() dRulesStatusChange: EventEmitter<any> = new EventEmitter<any>();\r\n\r\n @Input('dValidatePopConfig') popConfig: DPopConfig;\r\n\r\n popoverComponentRef: ComponentRef<PopoverComponent>;\r\n popMessage: string | TemplateRef<any>; // 最终显示的message\r\n private destroy$ = new Subject<void>();\r\n private _prevMessage: string;\r\n\r\n get showType() {\r\n return (this.fullRules as { messageShowType: string }).messageShowType || 'popover';\r\n }\r\n\r\n get popPosition() {\r\n return (this.fullRules as { popPosition: any }).popPosition || ['right', 'bottom'];\r\n }\r\n\r\n constructor(\r\n @Self() cd: NgControl,\r\n @Optional() @Host() private dFormItem: FormItemComponent,\r\n @Optional() @Host() @SkipSelf() parentDir: DFormGroupRuleDirective,\r\n private i18n: I18nService,\r\n public triggerElementRef: ElementRef,\r\n private overlayContainerRef: OverlayContainerRef,\r\n private componentFactoryResolver: ComponentFactoryResolver\r\n ) {\r\n super(cd, parentDir);\r\n }\r\n\r\n ngOnInit(): void {\r\n this.setI18nText();\r\n this._registerFocusChange();\r\n }\r\n\r\n setI18nText() {\r\n this.i18nFormText = this.i18n.getI18nText().form;\r\n this.locale = this.i18n.getI18nText().locale;\r\n\r\n this.i18n\r\n .langChange()\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe((data: I18nInterface) => {\r\n this.i18nFormText = data.form;\r\n this.locale = data.locale;\r\n });\r\n }\r\n\r\n _registerFocusChange() {\r\n merge(fromEvent(this.triggerElementRef.nativeElement, 'focusin'), fromEvent(this.triggerElementRef.nativeElement, 'focusout'))\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe((event: Event) => {\r\n if (event.type === 'focusin') {\r\n this.showPopMessage();\r\n }\r\n if (event.type === 'focusout') {\r\n this.hidePopMessage();\r\n }\r\n });\r\n }\r\n\r\n _updateFormContainer(status, message: string): void {\r\n if (this.dFormItem) {\r\n this.dFormItem.updateFeedback(status, message);\r\n }\r\n }\r\n\r\n _updatePopMessage(status: DFormControlStatus, message: string): void {\r\n this.popMessage = status === 'error' ? message : null; // 暂不提供除errorMessage外提示\r\n if (this.popoverComponentRef) {\r\n if (this._prevMessage !== this.popMessage) {\r\n this.hidePopMessage();\r\n this.showPopMessage();\r\n this._prevMessage = this.popMessage;\r\n }\r\n }\r\n }\r\n\r\n updateStatusAndMessageToView(status: any): void {\r\n let controlStatus = null;\r\n let message = null;\r\n if (this.showStatus) {\r\n [controlStatus, message] = this.getFormControlStatusAndMessage(status);\r\n }\r\n\r\n /* 国际化适配 */\r\n if (message && typeof message === 'object') {\r\n message = message[this.locale] || message.default || message;\r\n }\r\n\r\n if (this.showType === 'popover') {\r\n this._updatePopMessage(controlStatus, message);\r\n this._updateFormContainer(controlStatus, null);\r\n } else if (this.showType === 'text') {\r\n this._updateFormContainer(controlStatus, message);\r\n }\r\n }\r\n\r\n getFormControlStatusAndMessage(ngStatus: any) {\r\n let status = null;\r\n let message = null;\r\n if (ngStatus === 'INVALID') {\r\n status = 'error';\r\n message = this.errorMessage;\r\n } else if (ngStatus === 'PENDING') {\r\n status = 'pending';\r\n } else if (ngStatus === 'VALID') {\r\n status = 'success';\r\n }\r\n return [status, message];\r\n }\r\n\r\n createPopover(type: 'error' | 'warning', content: string) {\r\n this.popoverComponentRef = this.overlayContainerRef.createComponent(\r\n this.componentFactoryResolver.resolveComponentFactory(PopoverComponent)\r\n );\r\n Object.assign(this.popoverComponentRef.instance, {\r\n content: content,\r\n triggerElementRef: this.triggerElementRef,\r\n position: this.popPosition,\r\n popType: type,\r\n popMaxWidth: this.popConfig?.popMaxWidth || 200,\r\n scrollElement: this.popConfig?.scrollElement,\r\n zIndex: this.popConfig?.zIndex || 1060,\r\n showAnimation: this.popConfig?.showAnimation ?? true,\r\n appendToBody: true,\r\n });\r\n }\r\n\r\n public showPopMessage() {\r\n this.showPop('error', this.popMessage);\r\n if (this.popMessage) {\r\n this._updateFormContainer(null, null);\r\n }\r\n }\r\n\r\n public hidePopMessage() {\r\n this.hidePop();\r\n if (this.popMessage) {\r\n this._updateFormContainer(this.showError ? 'error' : null, null);\r\n }\r\n }\r\n\r\n showPop(type, message) {\r\n this.hidePop();\r\n this.createPopover(type, message);\r\n }\r\n\r\n hidePop() {\r\n if (this.popoverComponentRef) {\r\n this.destroyPop();\r\n }\r\n }\r\n\r\n destroyPop() {\r\n if (this.popoverComponentRef) {\r\n this.popoverComponentRef.destroy();\r\n this.popoverComponentRef = null;\r\n }\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.destroyPop();\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n}\r\n","import { ContentChildren, Directive, EventEmitter, HostBinding, Input, Optional, Output, QueryList, Self } from '@angular/core';\nimport { AbstractControl, AbstractControlDirective, ControlContainer, NgForm, ValidationErrors } from '@angular/forms';\nimport { filter, startWith, take } from 'rxjs/operators';\nimport { DFormControlRuleDirective, DFormGroupRuleDirective } from './validator-directive/form-control-rules.directive';\n\nexport enum FormLayout {\n Horizontal = 'horizontal',\n Vertical = 'vertical',\n Columns = 'columns',\n}\n\n@Directive({\n selector: '[dForm]',\n exportAs: 'dForm',\n})\nexport class FormDirective {\n @Input() layout = FormLayout.Horizontal;\n @Input() labelSize: 'sm' | '' | 'lg' = '';\n @Input() labelAlign: 'start' | 'center' | 'end' = 'start';\n\n /**\n * @deprecated Use dHasFeedback to replace, No longer support for label\n */\n @Input() dFeedbackType: 'label' | 'control';\n\n @Input() dHasFeedback = false;\n\n @Output() dSubmit = new EventEmitter();\n\n public readonly _cd: AbstractControlDirective;\n public readonly _dValidateRuleDir: DFormGroupRuleDirective;\n @ContentChildren(DFormControlRuleDirective, { descendants: true }) childrenCtrDirs: QueryList<DFormControlRuleDirective>;\n\n @HostBinding('class.devui-form-horizontal')\n get layoutHorizontal() {\n return this.layout === FormLayout.Horizontal;\n }\n\n @HostBinding('class.devui-form-vertical')\n get layoutVertical() {\n return this.layout === FormLayout.Vertical;\n }\n\n @HostBinding('class.devui-form-columns')\n get layoutColumns() {\n return this.layout === FormLayout.Columns;\n }\n\n @HostBinding('class.devui-form-lg')\n get labelSizeLg() {\n return this.labelSize === 'lg';\n }\n\n @HostBinding('class.devui-form-sm')\n get labelSizeSm() {\n return this.labelSize === 'sm';\n }\n\n @HostBinding('class.devui-form-label-align-start')\n get labelAlignStart() {\n return this.labelAlign === 'start';\n }\n\n @HostBinding('class.devui-form-label-align-center')\n get labelAlignCenter() {\n return this.labelAlign === 'center';\n }\n\n @HostBinding('class.devui-form-label-align-end')\n get labelAlignEnd() {\n return this.labelAlign === 'end';\n }\n\n updateOnSubmit($event?, data?: any) {\n this._operateAllControl(this._cd.control, (cd: AbstractControl) => {\n cd.markAsDirty();\n cd.updateValueAndValidity();\n });\n\n /* emit event should after validate */\n this._cd.control.statusChanges\n .pipe(\n startWith(this._cd.control.status),\n filter((status) => {\n return status !== 'PENDING';\n }),\n take(1)\n )\n .subscribe(() => {\n if (this._cd) {\n (this._cd as NgForm).onSubmit($event); // TODO: 需触发原生form表单的submit方法\n if (this._dValidateRuleDir) {\n this.dSubmit.emit({\n valid: this._dValidateRuleDir.isReady,\n directive: this._dValidateRuleDir,\n data: data,\n errors: this._getAllErrors(this._cd.control)\n });\n } else {\n this.dSubmit.emit({\n valid: this._cd.valid,\n directive: this._cd,\n data: data,\n errors: this._getAllErrors(this._cd.control)\n });\n }\n }\n\n if (this.childrenCtrDirs) {\n for (const validateDir of this.childrenCtrDirs) {\n if (validateDir.invalid && validateDir.showType === 'popover') {\n validateDir.showPopMessage(); // TODO: 表单类组件需要实现focus方法,若无focus,将无法正常blur\n break;\n }\n }\n }\n });\n }\n\n updateOnReset() {\n if (this._cd) {\n (this._cd as NgForm).onReset(); // TODO: 需触发原生form表单的reset方法\n }\n this._operateAllControl(this._cd.control, (cd: AbstractControl) => {\n cd.markAsPristine();\n cd.updateValueAndValidity();\n