UNPKG

@delon/form

Version:

Angular form generation based on JSON-Schema.

1 lines 245 kB
{"version":3,"file":"form.mjs","sources":["../../../../packages/form/src/config.ts","../../../../packages/form/src/const.ts","../../../../packages/form/src/utils.ts","../../../../packages/form/src/model/form.property.ts","../../../../packages/form/src/model/object.property.ts","../../../../packages/form/src/model/array.property.ts","../../../../packages/form/src/model/atomic.property.ts","../../../../packages/form/src/model/boolean.property.ts","../../../../packages/form/src/model/number.property.ts","../../../../packages/form/src/model/string.property.ts","../../../../packages/form/src/model/form.property.factory.ts","../../../../packages/form/src/terminator.service.ts","../../../../packages/form/src/validator.factory.ts","../../../../packages/form/src/widget.factory.ts","../../../../packages/form/src/sf-item.component.ts","../../../../packages/form/src/sf-fixed.directive.ts","../../../../packages/form/src/sf.component.ts","../../../../packages/form/src/sf.component.html","../../../../packages/form/src/sf-item-wrap.component.ts","../../../../packages/form/src/sf-item-wrap.component.html","../../../../packages/form/src/widgets/custom/sf-template.directive.ts","../../../../packages/form/src/widget.ts","../../../../packages/form/src/widgets/array/array.widget.ts","../../../../packages/form/src/widgets/boolean/boolean.widget.ts","../../../../packages/form/src/widgets/checkbox/checkbox.widget.ts","../../../../packages/form/src/widgets/custom/custom.widget.ts","../../../../packages/form/src/widgets/date/date.widget.ts","../../../../packages/form/src/widgets/number/number.widget.ts","../../../../packages/form/src/widgets/object/object.widget.ts","../../../../packages/form/src/widgets/radio/radio.widget.ts","../../../../packages/form/src/widgets/select/select.widget.ts","../../../../packages/form/src/widgets/string/string.widget.ts","../../../../packages/form/src/widgets/text/text.widget.ts","../../../../packages/form/src/widgets/textarea/textarea.widget.ts","../../../../packages/form/src/widgets/nz-widget.registry.ts","../../../../packages/form/src/module.ts","../../../../packages/form/src/errors.ts","../../../../packages/form/src/provide.ts","../../../../packages/form/form.ts"],"sourcesContent":["import { AlainConfigService, AlainSFConfig } from '@delon/util/config';\n\nimport { SFButton } from './interface';\nimport { SFUISchemaItem } from './schema/ui';\n\nexport const SF_DEFAULT_CONFIG: AlainSFConfig = {\n formatMap: {\n 'date-time': {\n widget: 'date',\n showTime: true,\n format: `yyyy-MM-dd'T'HH:mm:ss.SSSxxx`\n },\n date: { widget: 'date', format: 'yyyy-MM-dd' },\n 'full-date': { widget: 'date', format: 'yyyy-MM-dd' },\n time: { widget: 'time', format: 'HH:mm:ss.SSSxxx' },\n 'full-time': { widget: 'time' },\n week: { widget: 'date', mode: 'week', format: 'yyyy-ww' },\n month: { widget: 'date', mode: 'month', format: 'yyyy-MM' },\n uri: { widget: 'upload' },\n email: { widget: 'autocomplete', type: 'email' },\n color: { widget: 'string', type: 'color' },\n '': { widget: 'string' }\n },\n ingoreKeywords: ['type', 'enum'],\n liveValidate: true,\n autocomplete: null,\n firstVisual: false,\n onlyVisual: false,\n errors: {},\n ui: {} as SFUISchemaItem,\n button: { submit_type: 'primary', reset_type: 'default' } as SFButton,\n uiDateStringFormat: 'yyyy-MM-dd HH:mm:ss',\n uiDateNumberFormat: 'T',\n uiTimeStringFormat: 'HH:mm:ss',\n uiTimeNumberFormat: 'T',\n uiEmailSuffixes: ['qq.com', '163.com', 'gmail.com', '126.com', 'aliyun.com'],\n delay: false\n};\n\nexport function mergeConfig(srv: AlainConfigService): AlainSFConfig {\n return srv.merge('sf', SF_DEFAULT_CONFIG)!;\n}\n","export const SF_SEQ = '/';\n","import { Observable, of, map } from 'rxjs';\n\nimport { deepCopy } from '@delon/util/other';\nimport type { NzSafeAny } from 'ng-zorro-antd/core/types';\nimport { NzI18nService } from 'ng-zorro-antd/i18n';\n\nimport { SF_SEQ } from './const';\nimport type { SFValue } from './interface';\nimport type { SFSchema, SFSchemaDefinition, SFSchemaEnum } from './schema';\nimport type { SFUISchema, SFUISchemaItem, SFUISchemaItemRun, SFVisibleIf } from './schema/ui';\n\nexport function isBlank(o: NzSafeAny): boolean {\n return o == null;\n}\n\nexport function toBool(value: NzSafeAny, defaultValue: boolean): boolean {\n return value == null ? defaultValue : `${value}` !== 'false';\n}\n\nexport function di(ui: SFUISchema, ...args: NzSafeAny[]): void {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (ui.debug) {\n console.warn(...args);\n }\n }\n}\n\n/** 根据 `$ref` 查找 `definitions` */\nfunction findSchemaDefinition($ref: string, definitions: SFSchemaDefinition): NzSafeAny {\n const match = /^#\\/definitions\\/(.*)$/.exec($ref);\n if (match && match[1]) {\n // parser JSON Pointer\n const parts = match[1].split(SF_SEQ);\n let current: NzSafeAny = definitions;\n for (let part of parts) {\n part = part.replace(/~1/g, SF_SEQ).replace(/~0/g, '~');\n if (Object.prototype.hasOwnProperty.call(current, part)) {\n current = current[part];\n } else {\n throw new Error(`Could not find a definition for ${$ref}.`);\n }\n }\n return current;\n }\n throw new Error(`Could not find a definition for ${$ref}.`);\n}\n\n/**\n * 取回Schema,并处理 `$ref` 的关系\n */\nexport function retrieveSchema(schema: SFSchema, definitions: SFSchemaDefinition = {}): SFSchema {\n if (Object.prototype.hasOwnProperty.call(schema, '$ref')) {\n const $refSchema = findSchemaDefinition(schema.$ref!, definitions);\n // remove $ref property\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { $ref, ...localSchema } = schema;\n return retrieveSchema({ ...$refSchema, ...localSchema }, definitions);\n }\n\n return schema;\n}\n\nexport function resolveIfSchema(_schema: SFSchema, _ui: SFUISchemaItemRun): void {\n const fn = (schema: SFSchema, ui: SFUISchemaItemRun): void => {\n resolveIf(schema, ui);\n\n Object.keys(schema.properties!).forEach(key => {\n const property = schema.properties![key];\n const uiKey = `$${key}`;\n if (property.items) {\n fn(property.items, ui[uiKey].$items);\n }\n if (property.properties) {\n fn(property, ui[uiKey]);\n }\n });\n };\n fn(_schema, _ui);\n}\n\nfunction resolveIf(schema: SFSchema, ui: SFUISchemaItemRun): SFSchema | null {\n if (!(Object.prototype.hasOwnProperty.call(schema, 'if') && Object.prototype.hasOwnProperty.call(schema, 'then')))\n return null;\n if (!schema.if!.properties) throw new Error(`if: does not contain 'properties'`);\n\n const allKeys = Object.keys(schema.properties!);\n const ifKeys = Object.keys(schema.if!.properties!);\n detectKey(allKeys, ifKeys);\n detectKey(allKeys, schema.then!.required!);\n schema.required = schema.required!.concat(schema.then!.required!);\n const hasElse = Object.prototype.hasOwnProperty.call(schema, 'else');\n if (hasElse) {\n detectKey(allKeys, schema.else!.required!);\n schema.required = schema.required.concat(schema.else!.required!);\n }\n\n const visibleIf: SFVisibleIf = {};\n const visibleElse: SFVisibleIf = {};\n ifKeys.forEach(key => {\n const cond = schema.if!.properties![key].enum;\n visibleIf[key] = cond!;\n if (hasElse) visibleElse[key] = (value: SFValue) => !cond!.includes(value);\n });\n\n schema.then!.required!.forEach(key => (ui[`$${key}`].visibleIf = visibleIf));\n if (hasElse) {\n schema.else!.required!.forEach(key => (ui[`$${key}`].visibleIf = visibleElse));\n }\n\n return schema;\n}\n\nfunction detectKey(keys: string[], detectKeys: string[]): void {\n detectKeys.forEach(key => {\n if (!keys.includes(key)) {\n throw new Error(`if: properties does not contain '${key}'`);\n }\n });\n}\n\nexport function orderProperties(properties: string[], order: string[]): string[] {\n if (!Array.isArray(order)) return properties;\n const arrayToHash = (arr: NzSafeAny): NzSafeAny =>\n arr.reduce((prev: NzSafeAny, curr: NzSafeAny) => {\n prev[curr] = true;\n return prev;\n }, {});\n const errorPropList = (arr: NzSafeAny): string => `property [${arr.join(`', '`)}]`;\n\n const propertyHash = arrayToHash(properties);\n const orderHash = arrayToHash(order);\n const extraneous = order.filter(prop => prop !== '*' && !propertyHash[prop]);\n if (extraneous.length) {\n throw new Error(`ui schema order list contains extraneous ${errorPropList(extraneous)}`);\n }\n const rest = properties.filter(prop => !orderHash[prop]);\n const restIndex = order.indexOf('*');\n if (restIndex === -1) {\n if (rest.length) {\n throw new Error(`ui schema order list does not contain ${errorPropList(rest)}`);\n }\n return order;\n }\n if (restIndex !== order.lastIndexOf('*')) {\n throw new Error('ui schema order list contains more than one wildcard item');\n }\n const complete = [...order];\n complete.splice(restIndex, 1, ...rest);\n return complete;\n}\n\nexport function getEnum(list: NzSafeAny[], formData: NzSafeAny, readOnly: boolean): SFSchemaEnum[] {\n if (isBlank(list) || !Array.isArray(list) || list.length === 0) return [];\n if (typeof list[0] !== 'object') {\n list = list.map((item: NzSafeAny) => {\n return { label: item, value: item } as SFSchemaEnum;\n });\n }\n if (formData) {\n if (!Array.isArray(formData)) formData = [formData];\n list.forEach((item: SFSchemaEnum) => {\n if (~formData.indexOf(item.value)) item.checked = true;\n });\n }\n // fix disabled status\n if (readOnly) {\n list.forEach((item: SFSchemaEnum) => (item.disabled = true));\n }\n return list;\n}\n\nexport function getCopyEnum(list: NzSafeAny[], formData: NzSafeAny, readOnly: boolean): SFSchemaEnum[] {\n return getEnum(deepCopy(list || []), formData, readOnly);\n}\n\nexport function getData(\n schema: SFSchema,\n ui: SFUISchemaItem,\n formData: NzSafeAny,\n asyncArgs?: NzSafeAny\n): Observable<SFSchemaEnum[]> {\n if (typeof ui.asyncData === 'function') {\n return ui.asyncData(asyncArgs).pipe(map((list: SFSchemaEnum[]) => getCopyEnum(list, formData, schema.readOnly!)));\n }\n return of(getCopyEnum(schema.enum!, formData, schema.readOnly!));\n}\n\n/**\n * Whether to using date-fns to format a date\n */\nexport function isDateFns(srv: NzI18nService): boolean {\n if (!srv) return false;\n const data = srv.getDateLocale();\n // Compatible date-fns v1.x & v2.x\n return data != null && !!data.formatDistance; // (!!data.distanceInWords || !!data.formatDistance);\n}\n","import { Injector, NgZone } from '@angular/core';\nimport { BehaviorSubject, combineLatest, Observable, distinctUntilChanged, map, take } from 'rxjs';\n\nimport { AlainSFConfig } from '@delon/util/config';\nimport { NzFormStatusService } from 'ng-zorro-antd/core/form';\nimport type { NzSafeAny } from 'ng-zorro-antd/core/types';\nimport type { NzFormControlStatusType } from 'ng-zorro-antd/form';\n\nimport { SF_SEQ } from '../const';\nimport type { ErrorData } from '../errors';\nimport type { SFFormValueChange, SFUpdateValueAndValidity, SFValue } from '../interface';\nimport type { SFSchema, SFSchemaType } from '../schema';\nimport type { SFUISchema, SFUISchemaItem, SFUISchemaItemRun, SFVisibleIfReturn } from '../schema/ui';\nimport { isBlank } from '../utils';\nimport { SchemaValidatorFactory } from '../validator.factory';\nimport type { Widget } from '../widget';\n\nexport abstract class FormProperty {\n private _errors: ErrorData[] | null = null;\n private _valueChanges = new BehaviorSubject<SFFormValueChange>({ path: null, pathValue: null, value: null });\n private _errorsChanges = new BehaviorSubject<ErrorData[] | null>(null);\n private _visible = true;\n private _visibilityChanges = new BehaviorSubject<boolean>(true);\n private _root: PropertyGroup;\n private _parent: PropertyGroup | null;\n _objErrors: Record<string, ErrorData[]> = {};\n schemaValidator: (value: SFValue) => ErrorData[];\n schema: SFSchema;\n ui: SFUISchema | SFUISchemaItemRun;\n formData: Record<string, unknown>;\n _value: SFValue = null;\n widget!: Widget<FormProperty, SFUISchemaItem>;\n path: string;\n propertyId?: string;\n\n constructor(\n private injector: Injector,\n schemaValidatorFactory: SchemaValidatorFactory,\n schema: SFSchema,\n ui: SFUISchema | SFUISchemaItem,\n formData: Record<string, unknown>,\n parent: PropertyGroup | null,\n path: string,\n private _options: AlainSFConfig\n ) {\n this.schema = schema;\n this.ui = ui;\n this.schemaValidator = schemaValidatorFactory.createValidatorFn(schema, {\n ingoreKeywords: this.ui.ingoreKeywords as string[],\n debug: (ui as SFUISchemaItem)!.debug!\n });\n this.formData = formData || schema.default;\n this._parent = parent;\n if (parent) {\n this._root = parent.root;\n } else {\n this._root = this as NzSafeAny;\n }\n this.path = path;\n }\n\n get valueChanges(): BehaviorSubject<SFFormValueChange> {\n return this._valueChanges;\n }\n\n get errorsChanges(): BehaviorSubject<ErrorData[] | null> {\n return this._errorsChanges;\n }\n\n get type(): SFSchemaType {\n return this.schema.type!;\n }\n\n get parent(): PropertyGroup | null {\n return this._parent;\n }\n\n get root(): PropertyGroup {\n return this._root;\n }\n\n get value(): SFValue {\n return this._value;\n }\n\n get errors(): ErrorData[] | null {\n return this._errors;\n }\n\n get visible(): boolean {\n return this._visible;\n }\n\n get valid(): boolean {\n return this._errors === null || this._errors.length === 0;\n }\n\n get options(): AlainSFConfig {\n return this._options;\n }\n\n /**\n * 设置值\n *\n * @param onlySelf `true` 只对当前字段更新值和校验;`false` 包含上级字段\n */\n abstract setValue(value: SFValue, onlySelf: boolean): void;\n\n /**\n * 重置值,默认值为 `schema.default`\n *\n * @param onlySelf `true` 只对当前字段更新值和校验;`false` 包含上级字段\n */\n abstract resetValue(value: SFValue, onlySelf: boolean): void;\n\n /**\n * @internal\n */\n abstract _hasValue(): boolean;\n\n /**\n * @internal\n */\n abstract _updateValue(): void;\n\n cd(onlySelf: boolean = false): void {\n this.widget?.detectChanges(onlySelf);\n }\n\n /**\n * 更新值且校验数据\n */\n updateValueAndValidity(options?: SFUpdateValueAndValidity): void {\n options = {\n onlySelf: false,\n emitValidator: true,\n emitValueEvent: true,\n updatePath: '',\n updateValue: null,\n ...options\n };\n this._updateValue();\n\n if (options.emitValueEvent) {\n options.updatePath = options.updatePath || this.path;\n options.updateValue = options.updateValue == null ? this.value : options.updateValue;\n this.valueChanges.next({ value: this.value, path: options.updatePath, pathValue: options.updateValue });\n }\n\n // `emitValidator` 每一次数据变更已经包含完整错误链路,后续父节点数据变更无须再触发校验\n if (options.emitValidator && this.ui.liveValidate === true) {\n this._runValidation();\n }\n\n if (this.parent && !options.onlySelf) {\n this.parent.updateValueAndValidity({ ...options, emitValidator: false });\n }\n }\n\n /** 根据路径搜索表单属性 */\n searchProperty(path: string): FormProperty | null {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let prop: FormProperty = this;\n let base: PropertyGroup | null = null;\n\n let result = null;\n if (path[0] === SF_SEQ) {\n base = this.findRoot();\n result = base.getProperty(path.substring(1));\n } else {\n while (result === null && prop.parent !== null) {\n prop = base = prop.parent;\n result = base.getProperty(path);\n }\n }\n return result!;\n }\n\n /** 查找根表单属性 */\n findRoot(): PropertyGroup {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let property: FormProperty = this;\n while (property.parent !== null) {\n property = property.parent;\n }\n return property as PropertyGroup;\n }\n\n // #region process errors\n\n private isEmptyData(value: Record<string, unknown>): boolean {\n if (isBlank(value)) return true;\n switch (this.type) {\n case 'string':\n return `${value}`.length === 0;\n }\n return false;\n }\n\n /**\n * @internal\n */\n _runValidation(): void {\n let errors: ErrorData[];\n // The definition of some rules:\n // 1. Should not ajv validator when is empty data and required fields\n // 2. Should not ajv validator when is empty data\n const isEmpty = this.isEmptyData(this._value);\n if (isEmpty && this.ui._required) {\n errors = [{ keyword: 'required' }];\n } else if (isEmpty) {\n errors = [];\n } else {\n errors = this.schemaValidator(this._value) || [];\n }\n const customValidator = (this.ui as SFUISchemaItemRun).validator;\n if (typeof customValidator === 'function') {\n const customErrors = customValidator(this.value, this, this.findRoot());\n if (customErrors instanceof Observable) {\n customErrors.subscribe(res => {\n this.setCustomErrors(errors, res);\n this.cd(false);\n });\n return;\n }\n this.setCustomErrors(errors, customErrors);\n return;\n }\n\n this._errors = errors;\n this.setErrors(this._errors);\n }\n\n private setCustomErrors(errors: ErrorData[], list: ErrorData[]): void {\n const hasCustomError = Array.isArray(list) && list.length > 0;\n if (hasCustomError) {\n list.forEach(err => {\n if (!err.message) {\n throw new Error(`The custom validator must contain a 'message' attribute to viewed error text`);\n }\n err.keyword = null;\n });\n }\n this._errors = hasCustomError ? errors.concat(...list) : errors;\n this.setErrors(this._errors);\n }\n\n /**\n * Set the current error message\n *\n * 设置当前错误消息\n *\n * @param emitFormat 若提供的消息带有 `{xx}` 会自动根据参数进行转化,包含自定义函数\n *\n * @example\n *\n * this.sf.getProperty('/name')?.setErrors({ keyword: 'required' });\n * this.sf.getProperty('/name')?.setErrors({ message: 'Please input your username!' });\n * this.sf.getProperty('/name')?.setErrors(); // Clean error\n */\n setErrors(errors: ErrorData | ErrorData[] = [], emitFormat: boolean = true): void {\n let arrErrs = Array.isArray(errors) ? errors : [errors];\n\n if (emitFormat && arrErrs && !this.ui.onlyVisual) {\n const l = (this.widget && this.widget.l.error) || {};\n arrErrs = arrErrs.map((err: ErrorData) => {\n let message: string | ((err: ErrorData) => string) =\n err.keyword == null && err.message\n ? err.message\n : (this.ui.errors || {})[err.keyword!] || this._options.errors![err.keyword!] || l[err.keyword!] || ``;\n\n if (message && typeof message === 'function') {\n message = message(err);\n }\n\n if (message) {\n if (~message.indexOf('{') && err.params) {\n message = message.replace(/{([.a-zA-Z0-9]+)}/g, (_v: string, key: string) => err.params![key] || '');\n }\n err.message = message;\n }\n return err;\n });\n }\n this._errors = arrErrs;\n this._errorsChanges.next(arrErrs);\n // Should send errors to parent field\n if (this._parent) {\n this._parent.setParentAndPlatErrors(arrErrs, this.path);\n }\n }\n\n setParentAndPlatErrors(errors: ErrorData[], path: string): void {\n this._objErrors[path] = errors;\n const platErrors: ErrorData[] = [];\n Object.keys(this._objErrors).forEach(p => {\n const property = this.searchProperty(p);\n if (property && !property.visible) return;\n platErrors.push(...this._objErrors[p]);\n });\n this.setErrors(platErrors, false);\n }\n\n // #endregion\n\n // #region condition\n\n /**\n * Set the hide or display of widget\n * 设置小部件的隐藏或显示\n */\n setVisible(visible: boolean): this {\n this._visible = visible;\n this._visibilityChanges.next(visible);\n // 渲染时需要重新触发 reset\n if (visible) {\n this.injector\n .get(NgZone)\n .onStable.pipe(take(1))\n .subscribe(() => {\n this.resetValue(this.value, true);\n });\n }\n return this;\n }\n\n _bindVisibility(): void {\n const visibleIf = (this.ui as SFUISchemaItem).visibleIf;\n if (typeof visibleIf === 'object' && Object.keys(visibleIf).length === 0) {\n this.setVisible(false);\n } else if (visibleIf != null) {\n const propertiesBinding: Array<Observable<boolean>> = [];\n for (const dependencyPath in visibleIf) {\n if (Object.prototype.hasOwnProperty.call(visibleIf, dependencyPath)) {\n const property = this.searchProperty(dependencyPath);\n if (property) {\n const valueCheck = property.valueChanges.pipe(\n map(res => {\n const vi = visibleIf[dependencyPath];\n if (typeof vi === 'function') {\n const viFnRes = vi(res.value, property);\n // 同步更新 required\n if (typeof viFnRes === 'object') {\n const fixViFnRes = { show: false, required: false, ...viFnRes } as SFVisibleIfReturn;\n const parentRequired = this.parent?.schema.required;\n if (parentRequired && this.propertyId) {\n const idx = parentRequired.findIndex(w => w === this.propertyId);\n if (fixViFnRes.required) {\n if (idx === -1) parentRequired.push(this.propertyId);\n } else {\n if (idx !== -1) parentRequired.splice(idx, 1);\n }\n this.ui._required = fixViFnRes.required;\n }\n return fixViFnRes.show;\n }\n return viFnRes;\n }\n if (vi.indexOf('$ANY$') !== -1) {\n return res.value && res.value.length > 0;\n } else {\n return vi.indexOf(res.value) !== -1;\n }\n })\n );\n const visibilityCheck = property._visibilityChanges;\n const and = combineLatest([valueCheck, visibilityCheck]).pipe(map(results => results[0] && results[1]));\n propertiesBinding.push(and);\n } else {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n console.warn(`Can't find property ${dependencyPath} for visibility check of ${this.path}`);\n }\n }\n }\n }\n\n combineLatest(propertiesBinding)\n .pipe(\n map(values => (this.ui.visibleIfLogical === 'and' ? values.every(v => v) : values.some(v => v))),\n distinctUntilChanged()\n )\n .subscribe(visible => this.setVisible(visible));\n }\n }\n\n // #endregion\n\n updateFeedback(status: NzFormControlStatusType = ''): void {\n this.ui.feedback = status;\n this.widget?.injector.get(NzFormStatusService).formStatusChanges.next({ status, hasFeedback: !!status });\n this.cd(true);\n }\n}\n\nexport abstract class PropertyGroup extends FormProperty {\n properties: Record<string, FormProperty> | FormProperty[] | null = null;\n\n getProperty(path: string): FormProperty | undefined {\n const subPathIdx = path.indexOf(SF_SEQ);\n const propertyId = subPathIdx !== -1 ? path.substring(0, subPathIdx) : path;\n\n let property = (this.properties as Record<string, FormProperty>)[propertyId];\n if (property !== null && subPathIdx !== -1 && property instanceof PropertyGroup) {\n const subPath = path.substring(subPathIdx + 1);\n property = (property as PropertyGroup).getProperty(subPath)!;\n }\n return property;\n }\n\n forEachChild(fn: (formProperty: FormProperty, str: string) => void): void {\n // eslint-disable-next-line @typescript-eslint/no-for-in-array\n for (const propertyId in this.properties) {\n if (Object.prototype.hasOwnProperty.call(this.properties, propertyId)) {\n const property = (this.properties as Record<string, FormProperty>)[propertyId];\n fn(property, propertyId);\n }\n }\n }\n\n forEachChildRecursive(fn: (formProperty: FormProperty) => void): void {\n this.forEachChild(child => {\n fn(child);\n if (child instanceof PropertyGroup) {\n (child as PropertyGroup).forEachChildRecursive(fn);\n }\n });\n }\n\n _bindVisibility(): void {\n super._bindVisibility();\n this._bindVisibilityRecursive();\n }\n\n private _bindVisibilityRecursive(): void {\n this.forEachChildRecursive(property => {\n property._bindVisibility();\n });\n }\n\n isRoot(): boolean {\n return this === this.root;\n }\n}\n","import { Injector } from '@angular/core';\n\nimport { AlainSFConfig } from '@delon/util/config';\nimport type { NzSafeAny } from 'ng-zorro-antd/core/types';\n\nimport { SFValue } from '../interface';\nimport { SFSchema } from '../schema/index';\nimport { SFUISchema, SFUISchemaItem } from '../schema/ui';\nimport { orderProperties } from '../utils';\nimport { SchemaValidatorFactory } from '../validator.factory';\nimport { FormProperty, PropertyGroup } from './form.property';\nimport { FormPropertyFactory } from './form.property.factory';\n\nexport class ObjectProperty extends PropertyGroup {\n private _propertiesId: string[] = [];\n\n get propertiesId(): string[] {\n return this._propertiesId;\n }\n\n constructor(\n injector: Injector,\n private formPropertyFactory: FormPropertyFactory,\n schemaValidatorFactory: SchemaValidatorFactory,\n schema: SFSchema,\n ui: SFUISchema | SFUISchemaItem,\n formData: NzSafeAny,\n parent: PropertyGroup | null,\n path: string,\n options: AlainSFConfig\n ) {\n super(injector, schemaValidatorFactory, schema, ui, formData, parent, path, options);\n this.createProperties();\n }\n\n private createProperties(): void {\n this.properties = {};\n this._propertiesId = [];\n let orderedProperties: string[];\n try {\n orderedProperties = orderProperties(Object.keys(this.schema.properties!), this.ui.order as string[]);\n } catch (e) {\n console.error(`Invalid ${this.schema.title || 'root'} object field configuration:`, e);\n }\n orderedProperties!.forEach(propertyId => {\n (this.properties as Record<string, FormProperty>)[propertyId] = this.formPropertyFactory.createProperty(\n this.schema.properties![propertyId],\n this.ui[`$${propertyId}`],\n ((this.formData || {}) as NzSafeAny)[propertyId],\n this,\n propertyId\n );\n this._propertiesId.push(propertyId);\n });\n }\n\n setValue(value: SFValue, onlySelf: boolean): void {\n const properties = this.properties as Record<string, FormProperty>;\n for (const propertyId in value) {\n if (Object.prototype.hasOwnProperty.call(value, propertyId) && properties[propertyId]) {\n (properties[propertyId] as FormProperty).setValue(value[propertyId], true);\n }\n }\n this.cd(onlySelf);\n this.updateValueAndValidity({ onlySelf, emitValueEvent: true });\n }\n\n resetValue(value: SFValue, onlySelf: boolean): void {\n value = value || this.schema.default || {};\n const properties = this.properties as Record<string, FormProperty>;\n for (const propertyId in this.schema.properties) {\n if (Object.prototype.hasOwnProperty.call(this.schema.properties, propertyId)) {\n properties[propertyId].resetValue(value[propertyId], true);\n }\n }\n this.cd(onlySelf);\n this.updateValueAndValidity({ onlySelf, emitValueEvent: true });\n }\n\n _hasValue(): boolean {\n return this.value != null && !!Object.keys(this.value).length;\n }\n\n _updateValue(): void {\n const value: SFValue = {};\n this.forEachChild((property, propertyId) => {\n if (property.visible && property._hasValue()) {\n value[propertyId] = property.value;\n }\n });\n this._value = value;\n }\n}\n","import { Injector } from '@angular/core';\n\nimport { AlainSFConfig } from '@delon/util/config';\nimport { deepCopy } from '@delon/util/other';\nimport type { NzSafeAny } from 'ng-zorro-antd/core/types';\n\nimport { SF_SEQ } from '../const';\nimport { SFValue } from '../interface';\nimport { SFSchema } from '../schema/index';\nimport { SFUISchema, SFUISchemaItem } from '../schema/ui';\nimport { SchemaValidatorFactory } from '../validator.factory';\nimport { FormProperty, PropertyGroup } from './form.property';\nimport { FormPropertyFactory } from './form.property.factory';\nimport { ObjectProperty } from './object.property';\n\nexport class ArrayProperty extends PropertyGroup {\n constructor(\n injector: Injector,\n private formPropertyFactory: FormPropertyFactory,\n schemaValidatorFactory: SchemaValidatorFactory,\n schema: SFSchema,\n ui: SFUISchema | SFUISchemaItem,\n formData: Record<string, unknown>,\n parent: PropertyGroup | null,\n path: string,\n options: AlainSFConfig\n ) {\n super(injector, schemaValidatorFactory, schema, ui, formData, parent, path, options);\n this.properties = [];\n }\n\n getProperty(path: string): FormProperty | undefined {\n const subPathIdx = path.indexOf(SF_SEQ);\n const pos = +(subPathIdx !== -1 ? path.substring(0, subPathIdx) : path);\n const list = this.properties as PropertyGroup[];\n if (isNaN(pos) || pos >= list.length) {\n return undefined;\n }\n const subPath = path.substring(subPathIdx + 1);\n return list[pos].getProperty(subPath);\n }\n\n setValue(value: SFValue, onlySelf: boolean): void {\n this.properties = [];\n this.clearErrors();\n this.resetProperties(value);\n this.cd(onlySelf);\n this.updateValueAndValidity({ onlySelf, emitValueEvent: true });\n }\n\n resetValue(value: SFValue, onlySelf: boolean): void {\n this._value = value || this.schema.default || [];\n this.setValue(this._value, onlySelf);\n }\n\n _hasValue(): boolean {\n return true;\n }\n\n _updateValue(): void {\n const value: NzSafeAny[] = [];\n this.forEachChild((property: FormProperty) => {\n if (property.visible) {\n value.push({ ...(this.widget?.cleanValue ? null : property.formData), ...property.value });\n }\n });\n this._value = value;\n }\n\n private addProperty(formData: Record<string, unknown>): FormProperty {\n const newProperty = this.formPropertyFactory.createProperty(\n deepCopy(this.schema.items!),\n deepCopy(this.ui.$items),\n formData,\n this as PropertyGroup\n ) as ObjectProperty;\n (this.properties as FormProperty[]).push(newProperty);\n return newProperty;\n }\n\n private resetProperties(formDatas: Array<Record<string, unknown>>): void {\n for (const item of formDatas) {\n const property = this.addProperty(item);\n property.resetValue(item, true);\n }\n }\n\n private clearErrors(property?: FormProperty): void {\n (property || this)._objErrors = {};\n }\n\n // #region actions\n\n add(formData: Record<string, unknown>): FormProperty {\n const newProperty = this.addProperty(formData);\n newProperty.resetValue(formData, false);\n return newProperty;\n }\n\n remove(index: number): void {\n const list = this.properties as FormProperty[];\n this.clearErrors();\n list.splice(index, 1);\n list.forEach((property, idx) => {\n property.path = [property.parent!.path, idx].join(SF_SEQ);\n this.clearErrors(property);\n // TODO: 受限于 sf 的设计思路,对于移除数组项需要重新对每个子项进行校验,防止错误被父级合并后引起始终是错误的现象\n if (property instanceof ObjectProperty) {\n property.forEachChild(p => {\n p.updateValueAndValidity({ emitValueEvent: false });\n });\n }\n });\n if (list.length === 0) {\n this.updateValueAndValidity();\n }\n }\n\n // #endregion\n}\n","import { SFValue } from '../interface';\nimport { FormProperty } from './form.property';\n\nexport abstract class AtomicProperty extends FormProperty {\n abstract fallbackValue(): SFValue;\n\n setValue(value: SFValue, onlySelf: boolean): void {\n this._value = value;\n this.cd(onlySelf);\n this.updateValueAndValidity({ onlySelf, emitValueEvent: true });\n }\n\n resetValue(value: SFValue, onlySelf: boolean): void {\n if (value == null) {\n value = this.schema.default !== undefined ? this.schema.default : this.fallbackValue();\n }\n this._value = value;\n\n this.updateValueAndValidity({ onlySelf, emitValueEvent: true });\n\n if (this.widget) {\n this.widget.reset(value);\n this.cd(onlySelf);\n }\n }\n\n _hasValue(): boolean {\n return this.fallbackValue() !== this.value;\n }\n\n _updateValue(): void {}\n}\n","import { SFValue } from '../interface';\nimport { AtomicProperty } from './atomic.property';\n\nexport class BooleanProperty extends AtomicProperty {\n fallbackValue(): SFValue {\n return null;\n }\n}\n","import { SFValue } from '../interface';\nimport { AtomicProperty } from './atomic.property';\n\nexport class NumberProperty extends AtomicProperty {\n fallbackValue(): null {\n return null;\n }\n\n setValue(value: SFValue, onlySelf: boolean): void {\n if (typeof value === 'string') {\n if (value.length) {\n value = value.indexOf('.') > -1 ? parseFloat(value) : parseInt(value, 10);\n } else {\n value = undefined;\n }\n }\n this._value = value;\n this.cd(onlySelf);\n this.updateValueAndValidity({ onlySelf, emitValueEvent: true });\n }\n}\n","import { SFValue } from '../interface';\nimport { AtomicProperty } from './atomic.property';\n\nexport class StringProperty extends AtomicProperty {\n fallbackValue(): null {\n return null;\n }\n\n setValue(value: SFValue, onlySelf: boolean): void {\n this._value = value == null ? '' : value;\n this.cd(onlySelf);\n this.updateValueAndValidity({ onlySelf, emitValueEvent: true });\n }\n}\n","import { Injector } from '@angular/core';\n\nimport { AlainConfigService, AlainSFConfig } from '@delon/util/config';\n\nimport { mergeConfig } from '../config';\nimport { SF_SEQ } from '../const';\nimport { SFSchema } from '../schema/index';\nimport { SFUISchema, SFUISchemaItem } from '../schema/ui';\nimport { retrieveSchema } from '../utils';\nimport { SchemaValidatorFactory } from '../validator.factory';\nimport { ArrayProperty } from './array.property';\nimport { BooleanProperty } from './boolean.property';\nimport { FormProperty, PropertyGroup } from './form.property';\nimport { NumberProperty } from './number.property';\nimport { ObjectProperty } from './object.property';\nimport { StringProperty } from './string.property';\n\nexport class FormPropertyFactory {\n private options: AlainSFConfig;\n constructor(\n private injector: Injector,\n private schemaValidatorFactory: SchemaValidatorFactory,\n cogSrv: AlainConfigService\n ) {\n this.options = mergeConfig(cogSrv);\n }\n\n createProperty(\n schema: SFSchema,\n ui: SFUISchema | SFUISchemaItem,\n formData: Record<string, unknown>,\n parent: PropertyGroup | null = null,\n propertyId?: string\n ): FormProperty {\n let newProperty: FormProperty | null = null;\n let path = '';\n if (parent) {\n path += parent.path;\n if (parent.parent !== null) {\n path += SF_SEQ;\n }\n switch (parent.type) {\n case 'object':\n path += propertyId;\n break;\n case 'array':\n path += ((parent as ArrayProperty).properties as PropertyGroup[]).length;\n break;\n default:\n throw new Error(`Instanciation of a FormProperty with an unknown parent type: ${parent.type}`);\n }\n } else {\n path = SF_SEQ;\n }\n\n if (schema.$ref) {\n const refSchema = retrieveSchema(schema, parent!.root.schema.definitions);\n newProperty = this.createProperty(refSchema, ui, formData, parent, path);\n } else {\n // fix required\n if (\n (propertyId && parent!.schema.required!.indexOf(propertyId.split(SF_SEQ).pop()!) !== -1) ||\n ui.showRequired === true\n ) {\n ui._required = true;\n }\n // fix title\n if (schema.title == null) {\n schema.title = propertyId;\n }\n // fix date\n if ((schema.type === 'string' || schema.type === 'number') && !schema.format && !(ui as SFUISchemaItem).format) {\n if ((ui as SFUISchemaItem).widget === 'date')\n ui._format = schema.type === 'string' ? this.options.uiDateStringFormat : this.options.uiDateNumberFormat;\n else if ((ui as SFUISchemaItem).widget === 'time')\n ui._format = schema.type === 'string' ? this.options.uiTimeStringFormat : this.options.uiTimeNumberFormat;\n } else {\n ui._format = ui.format;\n }\n switch (schema.type) {\n case 'integer':\n case 'number':\n newProperty = new NumberProperty(\n this.injector,\n this.schemaValidatorFactory,\n schema,\n ui,\n formData,\n parent,\n path,\n this.options\n );\n break;\n case 'string':\n newProperty = new StringProperty(\n this.injector,\n this.schemaValidatorFactory,\n schema,\n ui,\n formData,\n parent,\n path,\n this.options\n );\n break;\n case 'boolean':\n newProperty = new BooleanProperty(\n this.injector,\n this.schemaValidatorFactory,\n schema,\n ui,\n formData,\n parent,\n path,\n this.options\n );\n break;\n case 'object':\n newProperty = new ObjectProperty(\n this.injector,\n this,\n this.schemaValidatorFactory,\n schema,\n ui,\n formData,\n parent,\n path,\n this.options\n );\n break;\n case 'array':\n newProperty = new ArrayProperty(\n this.injector,\n this,\n this.schemaValidatorFactory,\n schema,\n ui,\n formData,\n parent,\n path,\n this.options\n );\n break;\n default:\n throw new TypeError(`Undefined type ${schema.type}`);\n }\n }\n\n newProperty.propertyId = propertyId;\n\n if (newProperty instanceof PropertyGroup) {\n this.initializeRoot(newProperty);\n }\n\n return newProperty;\n }\n\n private initializeRoot(rootProperty: PropertyGroup): void {\n // rootProperty.init();\n rootProperty._bindVisibility();\n }\n}\n","import { Subject } from 'rxjs';\n\nexport class TerminatorService {\n onDestroy: Subject<boolean>;\n\n constructor() {\n this.onDestroy = new Subject();\n }\n\n destroy(): void {\n this.onDestroy.next(true);\n }\n}\n","import { Injectable, NgZone, inject } from '@angular/core';\n\nimport Ajv from 'ajv';\nimport addFormats from 'ajv-formats';\n\nimport { AlainConfigService, AlainSFConfig } from '@delon/util/config';\nimport { REGEX } from '@delon/util/format';\nimport type { NzSafeAny } from 'ng-zorro-antd/core/types';\n\nimport { mergeConfig } from './config';\nimport { ErrorData } from './errors';\nimport { SFValue } from './interface';\nimport { SFSchema } from './schema';\n\n@Injectable()\nexport abstract class SchemaValidatorFactory {\n abstract createValidatorFn(\n schema: SFSchema,\n extraOptions: { ingoreKeywords: string[]; debug: boolean }\n ): (value: SFValue) => ErrorData[];\n}\n\n@Injectable()\nexport class AjvSchemaValidatorFactory extends SchemaValidatorFactory {\n private readonly ngZone = inject(NgZone);\n private readonly cogSrv = inject(AlainConfigService);\n\n protected ajv!: Ajv;\n protected options!: AlainSFConfig;\n\n constructor() {\n super();\n if (!(typeof document === 'object' && !!document)) {\n return;\n }\n this.options = mergeConfig(this.cogSrv);\n const customOptions = this.options.ajv || {};\n this.ngZone.runOutsideAngular(() => {\n this.ajv = new Ajv({\n allErrors: true,\n loopEnum: 50,\n ...customOptions,\n formats: {\n 'data-url': /^data:([a-z]+\\/[a-z0-9-+.]+)?;name=(.*);base64,(.*)$/,\n color: REGEX.color,\n mobile: REGEX.mobile,\n 'id-card': REGEX.idCard,\n ...customOptions.formats\n }\n });\n addFormats(this.ajv as NzSafeAny);\n });\n }\n\n createValidatorFn(\n schema: SFSchema,\n extraOptions: { ingoreKeywords: string[]; debug: boolean }\n ): (value: SFValue) => ErrorData[] {\n const ingoreKeywords: string[] = [\n ...(this.options.ingoreKeywords as string[]),\n ...((extraOptions.ingoreKeywords as string[]) || [])\n ];\n\n return (value: SFValue): ErrorData[] => {\n try {\n this.ngZone.runOutsideAngular(() => this.ajv.validate(schema, value));\n } catch (e) {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n // swallow errors thrown in ajv due to invalid schemas, these\n // still get displayed\n if (extraOptions.debug) {\n console.warn(e);\n }\n }\n }\n let errors = this.ajv.errors;\n if (this.options && ingoreKeywords && errors) {\n errors = errors.filter(w => ingoreKeywords.indexOf(w.keyword) === -1);\n }\n return errors as ErrorData[];\n };\n }\n}\n","import { ComponentRef, Injectable, ViewContainerRef, inject } from '@angular/core';\n\nimport type { NzSafeAny } from 'ng-zorro-antd/core/types';\n\nimport { FormProperty } from './model/form.property';\nimport { SFUISchemaItem } from './schema/ui';\nimport type { Widget } from './widget';\n\nexport class WidgetRegistry {\n private _widgets: Record<string, Widget<FormProperty, SFUISchemaItem>> = {};\n\n private defaultWidget!: Widget<FormProperty, SFUISchemaItem>;\n\n get widgets(): Record<string, Widget<FormProperty, SFUISchemaItem>> {\n return this._widgets;\n }\n\n setDefault(widget: NzSafeAny): void {\n this.defaultWidget = widget;\n }\n\n register(type: string, widget: NzSafeAny): void {\n this._widgets[type] = widget;\n }\n\n has(type: string): boolean {\n return Object.prototype.hasOwnProperty.call(this._widgets, type);\n }\n\n getType(type: string): Widget<FormProperty, SFUISchemaItem> {\n if (this.has(type)) {\n return this._widgets[type];\n }\n return this.defaultWidget;\n }\n}\n\n@Injectable()\nexport class WidgetFactory {\n private readonly registry = inject(WidgetRegistry);\n\n createWidget(container: ViewContainerRef, type: string): ComponentRef<Widget<FormProperty, SFUISchemaItem>> {\n if (!this.registry.has(type)) {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n console.warn(`No widget for type \"${type}\"`);\n }\n }\n\n const componentClass = this.registry.getType(type) as NzSafeAny;\n return container.createComponent(componentClass);\n }\n}\n","import {\n Component,\n ComponentRef,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n TemplateRef,\n ViewChild,\n ViewContainerRef,\n ViewEncapsulation,\n inject\n} from '@angular/core';\nimport { Subject } from 'rxjs';\n\nimport { NzFormStatusService } from 'ng-zorro-antd/core/form';\n\nimport { FormProperty } from './model/form.property';\nimport { SFUISchemaItem } from './schema/ui';\nimport { TerminatorService } from './terminator.service';\nimport type { Widget } from './widget';\nimport { WidgetFactory } from './widget.factory';\n\nlet nextUniqueId = 0;\n\n@Component({\n selector: 'sf-item',\n exportAs: 'sfItem',\n host: { '[class.sf__item]': 'true' },\n template: `\n <ng-template #target />\n <ng-container *ngTemplateOutlet=\"footer\" />\n `,\n preserveWhitespaces: false,\n encapsulation: ViewEncapsulation.None,\n providers: [NzFormStatusService],\n // eslint-disable-next-line @angular-eslint/prefer-standalone\n standalone: false\n})\nexport class SFItemComponent implements OnInit, OnChanges, OnDestroy {\n private readonly widgetFactory = inject(WidgetFactory);\n private readonly terminator = inject(TerminatorService);\n\n private ref!: ComponentRef<Widget<FormProperty, SFUISchemaItem>>;\n readonly destroy$ = new Subject<void>();\n widget: Widget<FormProperty, SFUISchemaItem> | null = null;\n\n @Input() formProperty!: FormProperty;\n @Input() footer: TemplateRef<void> | null = null;\n\n @ViewChild('target', { read: ViewContainerRef, static: true })\n private container!: ViewContainerRef;\n\n onWidgetInstanciated(widget: Widget<FormProperty, SFUISchemaItem>): void {\n this.widget = widget;\n const id = `_sf-${nextUniqueId++}`;\n\n const ui = this.formProperty.ui as SFUISchemaItem;\n this.widget.formProperty = this.formProperty;\n this.widget.schema = this.formProperty.schema;\n this.widget.ui = ui;\n this.widget.id = id;\n this.formProperty.widget = widget;\n }\n\n ngOnInit(): void {\n this.terminator.onDestroy.subscribe(() => this.ngOnDestroy());\n }\n\n ngOnChanges(): void {\n const p = this.formProperty;\n this.ref = this.widgetFactory.createWidget(this.container, (p.ui.widget || p.schema.type) as string);\n this.onWidgetInstanciated(this.ref.instance);\n }\n\n ngOnDestroy(): void {\n const { destroy$ } = this;\n destroy$.next();\n destroy$.complete();\n this.ref.destroy();\n }\n}\n","import {\n AfterViewInit,\n Directive,\n ElementRef,\n Input,\n OnChanges,\n Renderer2,\n inject,\n numberAttribute\n} from '@angular/core';\n\n@Directive({\n selector: '[fixed-label]',\n // eslint-disable-next-line @angular-eslint/prefer-standalone\n standalone: false\n})\nexport class SFFixedDirective implements AfterViewInit, OnChanges {\n private readonly el: HTMLElement = inject(ElementRef).nativeElement;\n private readonly render = inject(Renderer2);\n\n private _inited = false;\n\n @Input({ alias: 'fixed-label', transform: (v: unknown) => numberAttribute(v, 0) }) num?: number | null;\n\n private init(): void {\n if (!this._inited || this.num == null || this.num <= 0) return;\n const el = this.el;\n const widgetEl = el.querySelector<HTMLElement>('.ant-row') || el;\n this.render.addClass(widgetEl, 'sf__fixed');\n const labelEl = widgetEl.querySelector('.ant-form-item-label');\n const controlEl = widgetEl.querySelector('.ant-form-item-control-wrapper,.ant-form-item-control');\n const unit = `${this.num}px`;\n if (labelEl) {\n this.render.setStyle(labelEl, 'flex', `0 0 ${unit}`);\n this.render.setStyle(controlEl, 'max-width', `calc(100% - ${unit})`);\n } else {\n this.render.setStyle(controlEl, 'margin-left', unit);\n }\n }\n\n ngAfterViewInit(): void {\n this._inited = true;\n this.init();\n }\n\n ngOnChanges(): void {\n if (this._inited) this.init();\n }\n}\n","import { Platform } from '@angular/cdk/platform';\nimport {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n Injector,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n SimpleChange,\n SimpleChanges,\n TemplateRef,\n ViewEncapsulation,\n booleanAttribute,\n inject\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { merge, filter } from 'rxjs';\n\nimport { ACLService } from '@delon/acl';\nimport { ALAIN_I18N_TOKEN, DelonLocaleService, LocaleData } from '@delon/theme';\nimport { AlainConfigService, AlainSFConfig } from '@delon/util/config';\nimport { deepCopy } from '@delon/util/other';\nimport type { NzSafeAny } from 'ng-zorro-antd/core/types';\nimport type { NzFormControlStatusType } from 'ng-zorro-antd/form';\n\nimport { mergeConfig } from './config';\nimport { SF_SEQ } from './const';\nimport type { ErrorData } from './errors';\nimport type { SFButton, SFLayout, SFMode, SFValueChange } from './interface';\nimport { FormProperty, PropertyGroup } from './model/form.property';\nimport { FormPropertyFactory } from './model/form.property.factory';\nimport type { SFSchema } from './schema/index';\nimport type { SFOptionalHelp, SFUISchema, SFUISchemaItem, SFUISchemaItemRun } from './schema/ui';\nimport { TerminatorService } from './terminator.service';\nimport { di, resolveIfSchema, retrieveSchema } from './utils';\nimport { SchemaValidatorFactory } from './validator.factory';\nimport { WidgetFactory } from './widget.factory';\n\nexport function useFactory(\n injector: Injector,\n schemaValidatorFactory: SchemaValidatorFactory,\n cogSrv: AlainConfigService\n): FormPropertyFactory {\n return new FormPropertyFactory(injector, schemaValidatorFactory, cogSrv);\n}\n\n@Component({\n selector: 'sf, [sf]',\n exportAs: 'sf',\n templateUrl: './sf.component.html',\n providers: [\n WidgetFactory,\n {\n provide: FormPropertyFactory,\n useFactory,\n deps: [Injector, SchemaValidatorFactory, AlainConfigService]\n },\n TerminatorService\n ],\n host: {\n '[class.sf]': 'true',\n '[class.sf__inline]': `layout === 'inline'`,\n '[class.sf__horizontal]': `layout === 'horizontal'`,\n '[class.sf__search]': `mode === 'search'`,\n '[class.sf__edit]': `mode === 'edit'`,\n '[class.sf__no-error]': `onlyVisual`,\n '[class.sf__no-colon]': `noColon`,\n '[class.sf__compact]': `compact`\n },\n preserveWhitespaces: false,\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n // eslint-disable-next-line