UNPKG

@fluent-form/core

Version:

An Angular dynamic forms library powered by Fluent API and JSON.

1 lines 167 kB
{"version":3,"file":"fluent-form-core.mjs","sources":["../../../packages/core/src/lib/directives/template-ref-holder.directive.ts","../../../packages/core/src/lib/components/form-content.ts","../../../packages/core/src/lib/tokens.ts","../../../packages/core/src/lib/utils/is.utils.ts","../../../packages/core/src/lib/services/evaluator.service.ts","../../../packages/core/src/lib/services/value-transformer.service.ts","../../../packages/core/src/lib/errors/runtime.ts","../../../packages/core/src/lib/patcher/provider.ts","../../../packages/core/src/lib/schemas/interfaces.ts","../../../packages/core/src/lib/utils/schema.utils.ts","../../../packages/core/src/lib/utils/value.utils.ts","../../../packages/core/src/lib/utils/form.utils.ts","../../../packages/core/src/lib/utils/model.utils.ts","../../../packages/core/src/lib/components/form/form.component.ts","../../../packages/core/src/lib/components/form/form.component.html","../../../packages/core/src/lib/components/widget-wrapper.ts","../../../packages/core/src/lib/compose/builder.ts","../../../packages/core/src/lib/compose/component-container.ts","../../../packages/core/src/lib/compose/control.ts","../../../packages/core/src/lib/compose/control-container.ts","../../../packages/core/src/lib/services/destroyed.service.ts","../../../packages/core/src/lib/services/widget-template-registry.service.ts","../../../packages/core/src/lib/services/widget-wrapper-template-registry.service.ts","../../../packages/core/src/lib/directives/binding.directive.ts","../../../packages/core/src/lib/directives/context-guard.directive.ts","../../../packages/core/src/lib/directives/control-wrapper.directive.ts","../../../packages/core/src/lib/breakpoints.ts","../../../packages/core/src/lib/directives/grid/col/col.component.ts","../../../packages/core/src/lib/directives/grid/row/row.component.ts","../../../packages/core/src/lib/directives/grid/module.ts","../../../packages/core/src/lib/directives/render/models/control-container.ts","../../../packages/core/src/lib/directives/render/form.directive.ts","../../../packages/core/src/lib/directives/render/outlet.directive.ts","../../../packages/core/src/lib/directives/render/module.ts","../../../packages/core/src/lib/directives/template-outlet.ts","../../../packages/core/src/lib/directives/template.directive.ts","../../../packages/core/src/lib/directives/with-injector.directive.ts","../../../packages/core/src/lib/pipes/shared.ts","../../../packages/core/src/lib/pipes/class.pipe.ts","../../../packages/core/src/lib/pipes/col.pipe.ts","../../../packages/core/src/lib/pipes/control.pipe.ts","../../../packages/core/src/lib/pipes/inject.pipe.ts","../../../packages/core/src/lib/pipes/invoke.pipe.ts","../../../packages/core/src/lib/pipes/new.pipe.ts","../../../packages/core/src/lib/pipes/reactive.pipe.ts","../../../packages/core/src/lib/pipes/renderable.pipe.ts","../../../packages/core/src/lib/pipes/schema-type.pipe.ts","../../../packages/core/src/lib/pipes/schema.pipe.ts","../../../packages/core/src/lib/pipes/style.pipe.ts","../../../packages/core/src/lib/pipes/template.pipe.ts","../../../packages/core/src/lib/pipes/widget-template.pipe.ts","../../../packages/core/src/lib/directives/wrapper/next-widget-wrapper-outlet.directive.ts","../../../packages/core/src/lib/directives/wrapper/widget-wrapper-outlet.directive.ts","../../../packages/core/src/lib/features/interface.ts","../../../packages/core/src/lib/features/helper.ts","../../../packages/core/src/lib/features/preloading.feature.ts","../../../packages/core/src/lib/features/schema-patcher.feature.ts","../../../packages/core/src/lib/features/static-expression.feature.ts","../../../packages/core/src/lib/widgets/widget.ts","../../../packages/core/src/lib/widgets/row/row.widget.ts","../../../packages/core/src/lib/widgets/row/row.widget.html","../../../packages/core/src/lib/widgets/use.ts","../../../packages/core/src/lib/features/widget-configs.feature.ts","../../../packages/core/src/lib/module.ts","../../../packages/core/src/lib/provider.ts","../../../packages/core/src/fluent-form-core.ts"],"sourcesContent":["import { Directive, TemplateRef, ViewChild } from '@angular/core';\n\n@Directive()\nexport abstract class TemplateRefHolder<C> {\n @ViewChild(TemplateRef, { static: true }) templateRef!: TemplateRef<C>;\n}\n","import { Directive } from '@angular/core';\nimport type { FormGroup } from '@angular/forms';\nimport type { AnyObject } from '@ngify/core';\nimport { TemplateRefHolder } from '../directives/template-ref-holder.directive';\nimport type { AbstractSchema } from '../schemas';\n\nexport interface FormContentTemplateContext {\n form: FormGroup;\n model: AnyObject;\n schema: AbstractSchema;\n onSubmit: (event: SubmitEvent) => boolean;\n}\n\n@Directive()\nexport abstract class AbstractFormContentComponent extends TemplateRefHolder<FormContentTemplateContext> { }\n","import { InjectionToken, type TemplateRef, type Type } from '@angular/core';\nimport type { SafeAny } from '@ngify/core';\nimport type { AbstractFormContentComponent, AbstractWidgetWrapper } from './components';\nimport type { SchemaConfig } from './interfaces';\nimport type { AbstractWidget } from './widgets/widget';\n\ndeclare const ngDevMode: boolean | undefined;\n\nexport const WIDGET_MAP = new InjectionToken<Map<string, () => Promise<Type<AbstractWidget<unknown>>>>>(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'WIDGET_MAP' : ''\n);\n\nexport const SCHEMA_MAP = new InjectionToken<Map<string, SchemaConfig<SafeAny>>>(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'SCHEMA_MAP' : ''\n);\n\nexport const NAMED_TEMPLATES = new InjectionToken<{ name: string, templateRef: TemplateRef<SafeAny> }[]>(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'NAMED_TEMPLATES' : ''\n);\n\nexport const FLUENT_FORM_CONTENT = new InjectionToken<Type<AbstractFormContentComponent>>(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'FLUENT_FORM_CONTENT' : ''\n);\nexport const FLUENT_WIDGET_WRAPPERS = new InjectionToken<Type<AbstractWidgetWrapper>[]>(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'FLUENT_WIDGET_WRAPPERS' : ''\n);\n","import type { SafeAny } from '@ngify/core';\n\nexport const isObject = (o: unknown): o is object => typeof o === 'object';\nexport const isNumber = (o: unknown): o is number => typeof o === 'number';\nexport const isString = (o: unknown): o is string => typeof o === 'string';\nexport const isFunction = (o: unknown): o is (...args: SafeAny) => SafeAny => typeof o === 'function';\nexport const isBoolean = (o: unknown): o is boolean => typeof o === 'boolean';\nexport const isUndefined = (o: unknown): o is undefined => o === undefined;\nexport const isArray = (o: unknown): o is SafeAny[] => Array.isArray(o);\n","import { Injectable } from '@angular/core';\nimport type { AnyObject, SafeAny } from '@ngify/core';\n\nconst RETURN_STR = 'return ';\nconst STATIC_EXPRESSION_PATTERN = /^{{.+}}$/;\nconst INTERPOLATION_PATTERN = /^{{|}}$/g;\n\nexport abstract class CodeEvaluator {\n abstract evaluate(code: string, context: AnyObject): SafeAny;\n}\n\nexport function isStaticExpression(str: string) {\n return STATIC_EXPRESSION_PATTERN.test(str);\n}\n\n@Injectable({ providedIn: 'root' })\nexport class DynamicCodeEvaluator implements CodeEvaluator {\n evaluate(code: string, context: AnyObject) {\n code = RETURN_STR + code.replace(INTERPOLATION_PATTERN, '');\n\n // TODO: This doesn't work with `unsafe-eval` enabled in CSP.\n const fn = new Function('o', `with(o){${code}}`);\n const proxy = new Proxy(Object.freeze(context), {\n // Intercept all properties to prevent lookup beyond the `Proxy` object's scope chain.\n has() {\n return true;\n },\n get(target, key, receiver) {\n if (key === Symbol.unscopables) {\n return undefined;\n }\n return Reflect.get(target, key, receiver);\n }\n });\n\n return fn(proxy);\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport type { AnyObject, SafeAny } from '@ngify/core';\nimport { isFunction, isString } from '../utils/is.utils';\nimport { CodeEvaluator, isStaticExpression } from './evaluator.service';\n\n/**\n * @internal\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class ValueTransformer {\n private readonly evaluator = inject(CodeEvaluator, { optional: true });\n\n transform(value: SafeAny, context: AnyObject = {}): SafeAny {\n if (isFunction(value)) {\n return value(context);\n }\n\n if (isString(value) && this.evaluator && isStaticExpression(value)) {\n return this.evaluator.evaluate(value, context);\n }\n\n return value;\n }\n}\n","export function throwWidgetNotFoundError(name: string | number): never {\n throw new Error(`The '${name}' widget was not found`);\n}\n\nexport function throwCustomTemplateNotFoundError(name: string): never {\n throw new Error(`The custom '${name}' template was not found`);\n}\n","import { InjectionToken, type Provider } from '@angular/core';\nimport type { SchemaPatcher } from './interfaces';\n\ndeclare const ngDevMode: boolean | undefined;\n\nexport const SCHEMA_PATCHERS = new InjectionToken<SchemaPatcher[]>(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'SchemaPatchers' : ''\n);\n\nexport function provideSchemaPatcher(patcher: SchemaPatcher): Provider {\n return {\n provide: SCHEMA_PATCHERS,\n useValue: patcher,\n multi: true\n };\n}\n","import { AbstractControl } from '@angular/forms';\nimport type { SafeAny } from '@ngify/core';\nimport type { AbstractSchema } from './abstract.schema';\nimport type { SchemaKey } from './types';\n\n/**\n * @public\n */\nexport enum SchemaType {\n Control,\n ControlGroup,\n ControlArray,\n ControlWrapper,\n Component,\n ComponentContainer,\n ComponentWrapper\n}\n\nexport const enum SchemaKind {\n Headless = 'headless',\n Headful = 'headful',\n Template = 'template',\n Row = 'row'\n}\n\nexport interface SchemaLike<Key extends SchemaKey = SchemaKey> {\n kind: string;\n key?: Key;\n}\n\nexport interface SchemaContext<S extends SchemaLike = AbstractSchema> {\n schema: S;\n /**\n * If there is no corresponding control, the parent control will be returned,\n * usually a `FormGroup` or `FormArray`.\n */\n control: AbstractControl;\n model: SafeAny;\n}\n\nexport type Length = number | { max?: number, min?: number };\n\nexport type SchemaReactiveFn<S extends AbstractSchema, R> = (ctx: SchemaContext<S>) => R;\n\nexport type MaybeSchemaReactiveFn<S extends AbstractSchema, R> = R | SchemaReactiveFn<S, R>;\n\nexport type WithoutSchemaReactiveFn<T extends MaybeSchemaReactiveFn<AbstractSchema, SafeAny>> = Exclude<T, SchemaReactiveFn<SafeAny, SafeAny>>;\n","import { inject, Injectable } from '@angular/core';\nimport { type ValidatorFn, Validators } from '@angular/forms';\nimport { throwWidgetNotFoundError } from '../errors';\nimport { SCHEMA_PATCHERS } from '../patcher';\nimport type {\n AbstractBranchSchema,\n AbstractComponentContainerSchema,\n AbstractComponentWrapperSchema,\n AbstractControlContainerSchema,\n AbstractControlSchema,\n AbstractControlWrapperSchema,\n AbstractSchema,\n SchemaKey,\n SingleSchemaKey\n} from '../schemas';\nimport { type SchemaLike, SchemaType } from '../schemas/interfaces';\nimport { SCHEMA_MAP } from '../tokens';\nimport type { Indexable } from '../types';\nimport { isArray, isString } from './is.utils';\n\ndeclare const ngDevMode: boolean | undefined;\n\nconst ANY_SCHEMA_SELECTOR = '*';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class SchemaUtil {\n private readonly schemaMap = inject(SCHEMA_MAP);\n private readonly schemaPatchers = inject(SCHEMA_PATCHERS, { optional: true }) ?? [];\n\n patch<T extends Indexable<AbstractSchema>>(schema: T): T {\n if (\n this.isControlWrapper(schema) ||\n this.isComponentWrapper(schema) ||\n this.isControlContainer(schema) ||\n this.isComponentContainer(schema)\n ) {\n schema.schemas = schema.schemas.map(schema => this.patch(schema));\n }\n\n const config = this.schemaMap.get(schema.kind);\n\n if (typeof ngDevMode !== 'undefined' && ngDevMode && !config) {\n throwWidgetNotFoundError(schema.kind);\n }\n\n const { type } = config!;\n\n return this.schemaPatchers\n .filter(patcher => {\n // Convert selector to an array for unified processing.\n const selector = isArray(patcher.selector) ? patcher.selector : [patcher.selector];\n\n return selector.some(kindOrType => {\n if (isString(kindOrType)) {\n return kindOrType === ANY_SCHEMA_SELECTOR || kindOrType === schema.kind;\n }\n\n return kindOrType === type;\n });\n })\n .reduce((patchedSchema, patcher) => {\n return patcher.patch(patchedSchema) as T;\n }, schema);\n }\n\n /**\n * Filter out top-level control/control container schemas.\n */\n filterControls(schemas: Indexable<AbstractSchema>[]) {\n return schemas.reduce((schemas, schema) => {\n if (this.isControlWrapper(schema) || this.isComponentContainer(schema)) {\n schemas = schemas.concat(this.filterControls(schema.schemas));\n } else if (this.isControl(schema) || this.isControlContainer(schema)) {\n schemas.push(schema);\n }\n\n return schemas;\n }, [] as (AbstractControlSchema | AbstractControlContainerSchema)[]);\n }\n\n isControlGroup(schema: SchemaLike): schema is AbstractControlContainerSchema {\n return this.typeOf(schema) === SchemaType.ControlGroup;\n }\n\n isControlArray(schema: SchemaLike): schema is AbstractControlContainerSchema {\n return this.typeOf(schema) === SchemaType.ControlArray;\n }\n\n isControlContainer(schema: SchemaLike): schema is AbstractControlContainerSchema {\n return this.isControlGroup(schema) || this.isControlArray(schema);\n }\n\n isControlWrapper(schema: SchemaLike): schema is AbstractControlWrapperSchema {\n return this.typeOf(schema) === SchemaType.ControlWrapper;\n }\n\n isControl(schema: SchemaLike): schema is AbstractControlSchema {\n return this.typeOf(schema) === SchemaType.Control;\n }\n\n isComponentContainer(schema: SchemaLike): schema is AbstractComponentContainerSchema {\n return this.typeOf(schema) === SchemaType.ComponentContainer;\n }\n\n isComponentWrapper(schema: SchemaLike): schema is AbstractComponentWrapperSchema {\n return this.typeOf(schema) === SchemaType.ComponentWrapper;\n }\n\n isComponent(schema: SchemaLike): schema is AbstractSchema {\n return this.typeOf(schema) === SchemaType.Component;\n }\n\n /**\n * Whether it is a multi-field schema.\n */\n isMultiKeySchema(schema: SchemaLike) {\n return isArray(schema.key);\n }\n\n /**\n * Whether it is a path field schema.\n */\n isPathKeySchema(schema: SchemaLike) {\n return isString(schema.key) && schema.key.includes('.');\n }\n\n /**\n * Non-control schema, indicating that it or its child nodes\n * do not contain any control schemas.\n */\n isNonControl(schema: SchemaLike): schema is AbstractSchema {\n return this.isComponent(schema) || this.isComponentWrapper(schema);\n }\n\n typeOf(schema: SchemaLike) {\n return this.schemaMap.get(schema.kind)?.type;\n }\n\n validatorsOf(schema: AbstractControlSchema) {\n const validators: ValidatorFn[] = this.schemaMap.get(schema.kind)?.validators?.(schema) ?? [];\n\n if (schema.required === true) {\n validators.push(Validators.required);\n }\n\n return validators;\n }\n\n parsePathKey(key: string) {\n return key.split('.');\n }\n\n norimalizePaths(paths: SingleSchemaKey | SchemaKey[]) {\n return (isArray(paths) ? paths : [paths]).map(o => o.toString());\n }\n\n find(schema: Indexable<AbstractBranchSchema>, key: SingleSchemaKey): AbstractSchema | null;\n find(schema: Indexable<AbstractBranchSchema>, key: SchemaKey[]): AbstractSchema | null;\n find(schema: Indexable<AbstractBranchSchema>, paths: SingleSchemaKey | SchemaKey[]): AbstractSchema | null;\n find(schema: Indexable<AbstractBranchSchema>, paths: SingleSchemaKey | SchemaKey[]): AbstractSchema | null {\n let currentSchema: Indexable<AbstractSchema> | null = schema;\n\n for (const path of this.norimalizePaths(paths)) {\n currentSchema = currentSchema['schemas'].find((o: AbstractSchema) => o.key?.toString() === path) ?? null;\n\n if (currentSchema === null) {\n return currentSchema;\n }\n }\n\n return currentSchema;\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport type { AbstractControl } from '@angular/forms';\nimport type { AnyArray, AnyObject } from '@ngify/core';\nimport type { AbstractControlSchema } from '../schemas';\nimport { isUndefined } from './is.utils';\nimport { SchemaUtil } from './schema.utils';\n\n/**\n * @internal\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class ValueUtil {\n private readonly schemaUtil = inject(SchemaUtil);\n\n valueOfModel<M extends AnyObject | AnyArray>(model: M, schema: AbstractControlSchema): unknown {\n let value: unknown;\n // If the value read from the model is undefined, it means the model did\n // not provide a value, so take the default value from the schema here.\n // If it is a multi-key schema, the values of these keys need to be retrieved\n // separately from the model and combined into an array.\n if (this.schemaUtil.isMultiKeySchema(schema)) {\n value = (schema.key as string[]).map((key, index) => {\n const val = model[key as keyof M];\n return isUndefined(val) ? schema.defaultValue?.[index] ?? null : val;\n });\n // If all elements in the array are `null`, assign `null` directly.\n if ((value as []).every(o => o === null)) {\n value = null;\n }\n } else if (this.schemaUtil.isPathKeySchema(schema)) {\n const paths = this.schemaUtil.parsePathKey(schema.key as string);\n value = paths.reduce((obj, key) => obj?.[key as keyof M] as M, model);\n } else {\n value = model[schema.key as keyof M];\n }\n\n if (isUndefined(value)) {\n value = schema.defaultValue ?? null;\n }\n\n if (schema.mapper) {\n return schema.mapper.parser(value, schema);\n }\n\n return value;\n }\n\n valueOfControl(control: AbstractControl, schema: AbstractControlSchema): unknown {\n const value = control.value;\n\n if (schema.mapper) {\n return schema.mapper.formatter(value, schema);\n }\n\n return value;\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { AbstractControl, type AbstractControlOptions, FormArray, FormControl, FormGroup, type ValidatorFn, Validators } from '@angular/forms';\nimport type { AnyArray, AnyObject, SafeAny } from '@ngify/core';\nimport type { AbstractControlSchema, AbstractFormArraySchema, AbstractFormGroupSchema, AbstractSchema, SchemaKey } from '../schemas';\nimport { ValueTransformer } from '../services/value-transformer.service';\nimport type { Indexable } from '../types';\nimport { isArray, isNumber, isUndefined } from './is.utils';\nimport { SchemaUtil } from './schema.utils';\nimport { ValueUtil } from './value.utils';\n\ndeclare const ngDevMode: boolean | undefined;\n\n@Injectable({\n providedIn: 'root'\n})\nexport class FormUtil {\n private readonly schemaUtil = inject(SchemaUtil);\n private readonly valueUtil = inject(ValueUtil);\n private readonly valueTransformer = inject(ValueTransformer);\n\n createFormControl(schema: AbstractControlSchema, model: AnyObject): FormControl {\n const validators: ValidatorFn[] = this.schemaUtil.validatorsOf(schema);\n const modalValue = this.valueUtil.valueOfModel(model, schema);\n\n const control = new FormControl(schema.defaultValue ?? null, {\n nonNullable: !isUndefined(schema.defaultValue),\n validators: schema.validators ? validators.concat(schema.validators) : validators,\n asyncValidators: schema.asyncValidators,\n updateOn: schema.config?.updateOn\n });\n\n if (modalValue !== null) {\n control.setValue(modalValue, { emitEvent: false });\n }\n\n return control;\n }\n\n createFormGroup(schema: AbstractFormGroupSchema, model: AnyObject): FormGroup;\n createFormGroup(schemas: Indexable<AbstractSchema>[], model: AnyObject): FormGroup;\n createFormGroup(schemaOrSchemas: AbstractFormGroupSchema | Indexable<AbstractSchema>[], model: AnyObject): FormGroup;\n createFormGroup(schemaOrSchemas: AbstractFormGroupSchema | Indexable<AbstractSchema>[], model: AnyObject): FormGroup {\n let schemas: Indexable<AbstractSchema>[];\n let options: AbstractControlOptions = {};\n\n if (isArray(schemaOrSchemas)) {\n schemas = schemaOrSchemas;\n } else {\n schemas = schemaOrSchemas.schemas;\n options = {\n validators: schemaOrSchemas.validators,\n asyncValidators: schemaOrSchemas.asyncValidators,\n updateOn: schemaOrSchemas.config?.updateOn\n };\n }\n\n return new FormGroup(\n this.createControlMap(schemas, model),\n options\n );\n }\n\n private createControlMap(schemas: Indexable<AbstractSchema>[], model: AnyObject) {\n return schemas.reduce((controls, schema) => {\n if (this.schemaUtil.isControlGroup(schema)) {\n const key = schema.key!.toString();\n controls[key] = this.createFormGroup(schema, model[key] ?? {});\n } else if (this.schemaUtil.isControlArray(schema)) {\n const key = schema.key!.toString();\n controls[key] = this.createFormArray(schema, model[key] ?? []);\n } else if (this.schemaUtil.isControlWrapper(schema) || this.schemaUtil.isComponentContainer(schema)) {\n Object.assign(controls, this.createControlMap(schema.schemas, model));\n } else if (this.schemaUtil.isControl(schema)) {\n if (this.schemaUtil.isPathKeySchema(schema)) {\n const paths = this.schemaUtil.parsePathKey(schema.key as string);\n const key = paths.pop()!;\n\n const parent: FormGroup = paths.reduce((previousGroup, path) => {\n let group = previousGroup.get(path) as FormGroup;\n\n if (!group) {\n group = new FormGroup({});\n previousGroup.addControl(path, group);\n }\n\n return group;\n }, (controls[paths.shift()!] ??= new FormGroup({})) as FormGroup);\n parent.addControl(key, this.createFormControl(schema, model));\n } else {\n controls[schema.key!.toString()] = this.createFormControl(schema, model);\n }\n }\n\n return controls;\n }, {} as Record<string, AbstractControl>);\n }\n\n createFormArray(schema: AbstractFormArraySchema, model: AnyArray): FormArray {\n const controls = this.createFormArrayElements(schema.schemas, model);\n const validators: ValidatorFn[] = schema.validators ?? [];\n\n if (schema.length) {\n validators.push(Validators.required);\n if (isNumber(schema.length)) {\n validators.push(\n Validators.minLength(schema.length),\n Validators.maxLength(schema.length)\n );\n } else {\n if (schema.length.min) {\n validators.push(Validators.minLength(schema.length.min));\n }\n if (schema.length.max) {\n validators.push(Validators.maxLength(schema.length.max));\n }\n }\n }\n\n return new FormArray<SafeAny>(controls, {\n validators,\n asyncValidators: schema.asyncValidators,\n updateOn: schema.config?.updateOn\n });\n }\n\n createFormArrayElements(schemas: Indexable<AbstractSchema>[], model: AnyArray) {\n // Only take the first one, ignore the rest.\n const [schema] = this.schemaUtil.filterControls(schemas);\n\n if (typeof ngDevMode !== 'undefined' && ngDevMode && !schema) {\n throw Error('FormArray element control schema not found');\n }\n\n return model.map((_, index) => {\n return this.createAnyControl({ ...schema, key: index }, model);\n });\n }\n\n createAnyControl(schema: AbstractControlSchema, model: AnyObject | AnyArray): AbstractControl {\n if (this.schemaUtil.isControlGroup(schema)) {\n return this.createFormGroup(schema, (model as AnyObject)[schema.key!] ?? {});\n }\n\n if (this.schemaUtil.isControlArray(schema)) {\n return this.createFormArray(schema, (model as AnyArray)[schema.key as number] ?? []);\n }\n\n return this.createFormControl(schema, model);\n }\n\n /**\n * Update the form state, currently includes:\n * - Updating validators\n * - Updating status - enabled / disabled\n * - Updating value\n * @param form\n * @param model\n * @param schemas\n */\n updateForm(form: FormGroup, model: AnyObject, schemas: AbstractSchema[]): void;\n updateForm(form: FormArray, model: AnyArray, schemas: AbstractSchema[]): void;\n updateForm(form: FormGroup | FormArray, model: AnyObject, schemas: AbstractSchema[]): void {\n for (const schema of schemas) {\n if (this.schemaUtil.isControlGroup(schema)) {\n const key = schema.key!;\n const formGroup = getChildControl(form, key) as FormGroup;\n\n this.updateForm(formGroup, model[key], schema.schemas);\n continue;\n }\n\n if (this.schemaUtil.isControlArray(schema)) {\n const key = schema.key!;\n const formArray = getChildControl(form, key) as FormArray;\n const [elementSchema] = this.schemaUtil.filterControls(schema.schemas);\n const elementSchemas = formArray.controls.map((_, index) => ({ ...elementSchema, key: index }));\n\n this.updateForm(formArray, model[schema.key!], elementSchemas);\n continue;\n }\n\n if (this.schemaUtil.isControlWrapper(schema) || this.schemaUtil.isComponentContainer(schema)) {\n this.updateForm(form as FormGroup, model, schema.schemas);\n continue;\n }\n\n if (this.schemaUtil.isControl(schema)) {\n const control = getChildControl(form, schema.key!)!;\n\n if (schema.value) {\n const value = this.valueTransformer.transform(schema.value, { model, schema, control });\n control.setValue(value, { onlySelf: true });\n }\n // update disabled\n const disabled = this.valueTransformer.transform(schema.disabled, { model, schema, control });\n if (control.enabled !== !disabled) { // Update only if inconsistent\n if (disabled) {\n // Using `onlySelf: true` here instead of `emitEvent: false` because the\n // control's own value/statusChanges events need to trigger properly.\n control.disable({ onlySelf: true });\n } else {\n control.enable({ onlySelf: true });\n }\n }\n // update required validator\n const required = this.valueTransformer.transform(schema.required, { model, schema, control });\n if (required) {\n control.addValidators(Validators.required);\n } else {\n control.removeValidators(Validators.required);\n }\n\n control.updateValueAndValidity({ emitEvent: false });\n }\n }\n }\n\n /**\n * Assign value from form to model\n * @param model\n * @param form\n * @param schemas\n */\n updateModel(model: AnyObject, form: FormGroup, schemas: AbstractSchema[]): AnyObject;\n updateModel(model: AnyArray, form: FormArray, schemas: AbstractSchema[]): AnyArray;\n updateModel(model: AnyObject, form: FormGroup | FormArray, schemas: AbstractSchema[]): AnyObject | AnyArray {\n for (const schema of schemas) {\n if (this.schemaUtil.isControlGroup(schema)) {\n const key = schema.key!;\n const formGroup = getChildControl(form, key) as FormGroup;\n\n this.updateModel(model[key] = {}, formGroup, schema.schemas);\n continue;\n }\n\n if (this.schemaUtil.isControlArray(schema)) {\n const key = schema.key!;\n const formArray = getChildControl(form, key) as FormArray;\n const [elementSchema] = this.schemaUtil.filterControls(schema.schemas);\n const elementSchemas = formArray.controls.map((_, index) => ({ ...elementSchema, key: index }));\n\n this.updateModel(model[key] = [], formArray, elementSchemas);\n continue;\n }\n\n if (this.schemaUtil.isControlWrapper(schema) || this.schemaUtil.isComponentContainer(schema)) {\n this.updateModel(model, form as FormGroup, schema.schemas);\n continue;\n }\n\n if (this.schemaUtil.isControl(schema)) {\n const key = schema.key!.toString();\n const control = getChildControl(form, key)!;\n const value = this.valueUtil.valueOfControl(control, schema);\n\n // Multi-field case.\n if (this.schemaUtil.isMultiKeySchema(schema)) {\n (schema.key as string[]).map((prop, idx) => {\n model[prop] = (value as [unknown, unknown])?.[idx] ?? null;\n });\n } else if (this.schemaUtil.isPathKeySchema(schema)) {\n const paths = this.schemaUtil.parsePathKey(schema.key as string);\n let _model = model;\n for (let i = 0; i < paths.length - 1; i++) {\n const path = paths[i];\n _model = _model[path] ??= {};\n }\n _model[paths.pop()!] = value;\n } else {\n model[key] = value;\n }\n }\n }\n\n return model;\n }\n}\n\n/**\n * Automatically choose to use `.get()` or `.at()` to get a child control\n * based on the type of the form.\n * @param form\n * @param key\n * @returns\n */\nexport function getChildControl(form: FormGroup | FormArray, key: SchemaKey): AbstractControl | null {\n return form instanceof FormArray ? form.at(key as number) : form.get(key.toString() as string);\n}\n","import { inject, Injectable } from '@angular/core';\nimport { FormArray, FormGroup } from '@angular/forms';\nimport type { AnyArray, AnyObject } from '@ngify/core';\nimport type { AbstractSchema } from '../schemas';\nimport type { Indexable } from '../types';\nimport { FormUtil, getChildControl } from './form.utils';\nimport { SchemaUtil } from './schema.utils';\nimport { ValueUtil } from './value.utils';\n\n/**\n * @internal\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class ModelUtil {\n private readonly schemaUtil = inject(SchemaUtil);\n private readonly valueUtil = inject(ValueUtil);\n private readonly formUtil = inject(FormUtil);\n\n /**\n * 从 model 赋值到 form\n * @param form\n * @param model\n * @param schemas\n * @param completed\n */\n updateForm(form: FormGroup, model: AnyObject, schemas: Indexable<AbstractSchema>[], completed?: boolean): FormGroup;\n updateForm(form: FormArray, model: AnyArray, schemas: Indexable<AbstractSchema>[], completed?: boolean): FormArray;\n updateForm(form: FormGroup | FormArray, model: AnyObject, schemas: Indexable<AbstractSchema>[], completed = true): FormGroup | FormArray {\n for (const schema of schemas) {\n if (this.schemaUtil.isControlGroup(schema)) {\n const key = schema.key!;\n const formGroup = getChildControl(form, key) as FormGroup;\n\n this.updateForm(formGroup, model[key] ??= {}, schema.schemas, false);\n continue;\n }\n\n if (this.schemaUtil.isControlArray(schema)) {\n const key = schema.key!;\n const array: AnyArray = model[key] ??= [];\n const formArray = getChildControl(form, key) as FormArray;\n\n // / If the model array length matches the form array length, update the\n // form values in place; otherwise, rebuild the form array.\n if (array.length === formArray.length) {\n const [elementSchema] = this.schemaUtil.filterControls(schema.schemas);\n const elementSchemas = array.map((_, index) => ({ ...elementSchema, key: index }));\n\n this.updateForm(formArray, array, elementSchemas, false);\n } else {\n const controls = this.formUtil.createFormArrayElements(schema.schemas, array);\n\n formArray.clear({ emitEvent: false });\n for (const control of controls) {\n formArray.push(control, { emitEvent: false });\n }\n formArray.updateValueAndValidity({ onlySelf: true });\n }\n continue;\n }\n\n if (this.schemaUtil.isComponentContainer(schema) || this.schemaUtil.isControlWrapper(schema)) {\n this.updateForm(form as FormGroup, model, schema.schemas, false);\n continue;\n }\n\n if (this.schemaUtil.isControl(schema)) {\n const value = this.valueUtil.valueOfModel(model, schema);\n const control = getChildControl(form, schema.key!)!;\n\n control.setValue(value, { onlySelf: true });\n }\n }\n\n // Only disable `onlySelf` after all child updates are complete.\n form.updateValueAndValidity({ onlySelf: !completed });\n\n return form;\n }\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n EnvironmentInjector,\n Injector,\n computed,\n createComponent,\n effect,\n inject,\n input,\n model,\n output,\n untracked\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { type FormControlStatus, FormGroup } from '@angular/forms';\nimport type { AnyObject } from '@ngify/core';\nimport type { AbstractFormGroupSchema } from '../../schemas';\nimport { FLUENT_FORM_CONTENT, NAMED_TEMPLATES } from '../../tokens';\nimport { FormUtil, ModelUtil, SchemaUtil } from '../../utils';\n\n@Component({\n selector: 'fluent-form',\n imports: [NgTemplateOutlet],\n templateUrl: './form.component.html',\n providers: [\n {\n provide: NAMED_TEMPLATES,\n useFactory: () => []\n }\n ],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class FluentForm<T extends AnyObject> {\n private readonly formUtil = inject(FormUtil);\n private readonly modelUtil = inject(ModelUtil);\n private readonly schemaUtil = inject(SchemaUtil);\n private internalModel!: T;\n\n protected readonly formContentRef = createComponent(\n inject(FLUENT_FORM_CONTENT),\n {\n environmentInjector: inject(EnvironmentInjector),\n elementInjector: inject(Injector)\n }\n );\n\n readonly schema = input.required<AbstractFormGroupSchema>();\n readonly model = model.required<T>();\n\n readonly patchedSchema = computed(() => this.schemaUtil.patch(this.schema()));\n readonly form = computed<FormGroup>(() =>\n this.formUtil.createFormGroup(this.patchedSchema(), untracked(this.model)));\n\n readonly formChange = output<FormGroup>();\n readonly valueChanges = output<T>();\n readonly statusChanges = output<FormControlStatus>();\n // eslint-disable-next-line @angular-eslint/no-output-native\n readonly submit = output<SubmitEvent>();\n\n readonly onSubmit = (event: SubmitEvent) => {\n event.stopPropagation();\n this.submit.emit(event);\n // Forms with `method=\"dialog\"` have some special behavior that won't reload the page and that\n // shouldn't be prevented. Note that we need to null check the `event` and the `target`, because\n // some internal apps call this method directly with the wrong arguments.\n return (event?.target as HTMLFormElement | null)?.method === 'dialog';\n };\n\n constructor() {\n const destroyRef = inject(DestroyRef);\n\n effect(() => {\n const form = this.form();\n untracked(() => {\n this.formChange.emit(form);\n this.onValueChanges();\n\n form.valueChanges.pipe(takeUntilDestroyed(destroyRef)).subscribe(value => {\n this.onValueChanges();\n this.valueChanges.emit(value);\n });\n\n form.statusChanges.pipe(takeUntilDestroyed(destroyRef)).subscribe(status =>\n this.statusChanges.emit(status));\n });\n });\n\n let modelChangeCounter = 0;\n effect(() => {\n const model = this.model();\n untracked(() => {\n // If the change comes from outside, assign the value to the form.\n if (modelChangeCounter++ > 0 && model !== this.internalModel) {\n this.modelUtil.updateForm(\n this.form(),\n model,\n this.patchedSchema().schemas\n );\n }\n });\n });\n\n destroyRef.onDestroy(() => this.formContentRef.destroy());\n }\n\n private onValueChanges() {\n const form = this.form();\n const { schemas } = this.patchedSchema();\n\n this.formUtil.updateForm(\n form,\n this.internalModel = this.formUtil.updateModel(\n {} as T,\n form,\n schemas\n ),\n schemas\n );\n this.model.set(this.internalModel);\n }\n}\n\n/**\n * @deprecated\n * This component will be removed in future versions. Please use {@link FluentForm} instead.\n */\nexport const FluentFormComponent = FluentForm;\n","<ng-container\n [ngTemplateOutlet]=\"formContentRef.instance.templateRef\"\n [ngTemplateOutletContext]=\"{ schema: patchedSchema(), model: model(), form: form(), onSubmit }\" />\n","import { Directive, TemplateRef } from '@angular/core';\nimport type { AbstractControl } from '@angular/forms';\nimport type { AnyObject } from '@ngify/core';\nimport { TemplateRefHolder } from '../directives/template-ref-holder.directive';\nimport type { AbstractSchema } from '../schemas';\n\nexport interface WidgetWrapperContext {\n control: AbstractControl;\n model: AnyObject;\n schema: AbstractSchema;\n templateRef: TemplateRef<WidgetWrapperContext>;\n next?: WidgetWrapperContext;\n}\n\n@Directive()\nexport abstract class AbstractWidgetWrapper extends TemplateRefHolder<WidgetWrapperContext> { }\n","import type { AnyObject, SafeAny } from '@ngify/core';\n\ninterface Schema {\n [k: string]: SafeAny;\n schemas?: Schema[];\n}\n\nconst COMPOSE_KEY = 'schemas';\nconst SCHEMA_STACK: Schema[] = [];\n\nexport function getCurrentSchema(): Schema | undefined {\n return SCHEMA_STACK[SCHEMA_STACK.length - 1];\n}\n\nfunction enterSchema(schema: Schema) {\n SCHEMA_STACK.push(schema);\n}\n\nfunction leaveSchema() {\n SCHEMA_STACK.pop();\n}\n\nfunction joinSchema(schema: Schema) {\n const currentSchema = getCurrentSchema();\n // If a schema already exists, just push it in as a subschema.\n currentSchema?.schemas!.push(schema);\n}\n\nconst IS_BUILDER = Symbol();\nconst BUILD_KEY = 'build';\n\nexport function composeBuilder<T extends Record<string, SafeAny>>(target: AnyObject = {}, append = true): Builder<T> {\n append && joinSchema(target);\n\n const builder = new Proxy(target, {\n get(target, property) {\n switch (property) {\n case IS_BUILDER: return true;\n case BUILD_KEY: return () => target;\n\n case COMPOSE_KEY:\n return (composeFn: (...args: SafeAny) => SafeAny): unknown => {\n target[COMPOSE_KEY] = [];\n\n enterSchema(target);\n\n try {\n composeFn(builder);\n } finally {\n leaveSchema();\n }\n\n return builder;\n };\n }\n\n return (value: unknown): unknown => {\n if (value !== target[property]) {\n target[property] = value;\n }\n return builder;\n };\n }\n });\n\n return builder as Builder<T>;\n}\n\n/**\n * Determines whether the value is a builder.\n */\nexport function isBuilder<T = unknown>(value: SafeAny): value is StableBuilder<T> {\n return value[IS_BUILDER] ?? false;\n}\n\ntype ComposeKey = typeof COMPOSE_KEY;\ntype BuildKey = typeof BUILD_KEY;\n\ntype RequiredKey<T> = {\n [K in keyof T]-?: undefined extends T[K] ? never : K\n}[keyof T];\n\ntype ComposeFn<T extends Record<string, SafeAny>> = (it: Builder<T>) => SafeAny;\n\n/**\n * @template T 原型\n * @template TCandidateKey 候选\n * @template TRequiredKey 必填字段\n * @template TSelectedKey 已选字段\n */\nexport type Builder<\n T extends Record<string, SafeAny>,\n TCandidateKey extends keyof T = keyof T,\n TRequiredKey extends keyof T = RequiredKey<T>,\n TSelectedKey extends keyof T = never\n> =\n {\n [K in keyof Pick<T, Exclude<TCandidateKey, TSelectedKey>>]-?: (\n (val: K extends ComposeKey ? ComposeFn<T> : T[K]) => Builder<\n T,\n TCandidateKey,\n TRequiredKey,\n TSelectedKey | K\n >\n )\n }\n &\n Record<[TRequiredKey] extends [TSelectedKey] ? BuildKey : never, () => Pick<T, TSelectedKey>>;\n\nexport type StableBuilder<T> = Record<BuildKey, () => T>;\n\n/**\n * @template T 原型\n * @template TRequiredKey 必填字段\n * @template TSelectedKey 已选字段\n */\nexport type UnstableBuilder<\n T extends Record<string, SafeAny>,\n TSelectedKey extends keyof T,\n TRequiredKey extends keyof T = RequiredKey<T>\n> = Builder<\n T,\n keyof T,\n TRequiredKey,\n TSelectedKey\n>;\n","import type { RowComponentSchema, SingleSchemaKey } from '../schemas';\nimport { type UnstableBuilder, composeBuilder } from './builder';\nimport type { KindOrKey } from './helper';\n\nexport function row<Key extends SingleSchemaKey>(key?: Key): UnstableBuilder<RowComponentSchema<Key>, KindOrKey> {\n return composeBuilder<RowComponentSchema<Key>>().kind('row').key(key);\n}\n","import type { HeadlessControlSchema, SingleSchemaKey } from '../schemas';\nimport { type UnstableBuilder, composeBuilder } from './builder';\nimport type { KindOrKey } from './helper';\n\nexport function headless<Key extends SingleSchemaKey>(key?: Key): UnstableBuilder<HeadlessControlSchema<Key>, KindOrKey> {\n return composeBuilder<HeadlessControlSchema<Key>>().kind('headless').key(key);\n}\n","import { type Signal, computed } from '@angular/core';\nimport type { AbstractFormGroupSchema, AbstractSchema } from '../schemas';\nimport type { Indexable } from '../types';\nimport { isArray } from '../utils';\nimport { composeBuilder } from './builder';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype SafeAny = any;\ntype ComposeFn = () => SafeAny;\n\nexport function form(composeFn: ComposeFn): Signal<AbstractFormGroupSchema>;\nexport function form(schemas: Indexable<AbstractSchema>[]): AbstractFormGroupSchema;\nexport function form(schemasOrComposeFn: Indexable<AbstractSchema>[] | ComposeFn): AbstractFormGroupSchema | Signal<AbstractFormGroupSchema> {\n if (isArray(schemasOrComposeFn)) {\n return {\n kind: 'group',\n key: 'root',\n schemas: schemasOrComposeFn\n };\n }\n\n return computed(() =>\n composeBuilder<AbstractFormGroupSchema>()\n .kind('group')\n .key('root')\n .schemas(schemasOrComposeFn)\n .build());\n}\n","import { DestroyRef, inject, Injectable } from '@angular/core';\nimport { Subject } from 'rxjs';\n\n@Injectable()\nexport class DestroyedSubject extends Subject<void> {\n constructor() {\n super();\n\n inject(DestroyRef).onDestroy(() => {\n this.next();\n this.complete();\n });\n }\n}\n","import { createComponent, EnvironmentInjector, inject, Injectable, TemplateRef } from '@angular/core';\nimport { throwWidgetNotFoundError } from '../errors';\nimport { WIDGET_MAP } from '../tokens';\n\ndeclare const ngDevMode: boolean | undefined;\n\n@Injectable({ providedIn: 'root' })\nexport class WidgetTemplateRegistry extends Map<string, Promise<TemplateRef<unknown>>> {\n private readonly envInjector = inject(EnvironmentInjector);\n private readonly widgetMap = inject(WIDGET_MAP);\n\n override get(kind: string): Promise<TemplateRef<unknown>> {\n return super.get(kind) ?? this.register(kind);\n }\n\n register(kind: string) {\n const component = this.widgetMap.get(kind);\n\n if (typeof ngDevMode !== 'undefined' && ngDevMode && !component) {\n throwWidgetNotFoundError(kind);\n }\n\n const tmpl = component!().then(comp => {\n const { instance } = createComponent(comp, {\n environmentInjector: this.envInjector\n });\n return instance.templateRef;\n });\n\n this.set(kind, tmpl);\n return tmpl;\n }\n}\n","import { createComponent, EnvironmentInjector, inject, Injectable, TemplateRef, Type } from '@angular/core';\nimport type { AbstractWidgetWrapper, WidgetWrapperContext } from '../components';\n\n@Injectable({ providedIn: 'root' })\nexport class WidgetWrapperTemplateRegistry extends Map<Type<AbstractWidgetWrapper>, TemplateRef<WidgetWrapperContext>> {\n private readonly envInjector = inject(EnvironmentInjector);\n\n override get(type: Type<AbstractWidgetWrapper>): TemplateRef<WidgetWrapperContext> {\n return super.get(type) ?? this.register(type);\n }\n\n register(type: Type<AbstractWidgetWrapper>) {\n const { templateRef } = createComponent(type, {\n environmentInjector: this.envInjector\n }).instance;\n\n this.set(type, templateRef);\n\n return templateRef;\n }\n}\n","import {\n DestroyRef,\n Directive,\n ElementRef,\n Injector,\n type OnInit,\n type OutputRef,\n Type,\n computed,\n effect,\n inject,\n input,\n isSignal,\n reflectComponentType,\n runInInjectionContext,\n untracked\n} from '@angular/core';\nimport { SIGNAL, type SignalNode, signalSetFn } from '@angular/core/primitives/signals';\nimport { outputToObservable } from '@angular/core/rxjs-interop';\nimport { AbstractControl } from '@angular/forms';\nimport type { AnyObject, SafeAny } from '@ngify/core';\nimport { Observable, fromEvent, map, takeUntil } from 'rxjs';\nimport type { AbstractSchema, EventListenerHolder, EventObserverHolder, HooksHolder, ListenerContext, PropertyHolder, SchemaContext } from '../schemas';\nimport { DestroyedSubject } from '../services';\n\nfunction isHookHolder(value: SafeAny): value is Required<HooksHolder> {\n return 'hooks' in value;\n}\n\nfunction isListenerHolder(value: SafeAny): value is Required<EventListenerHolder> {\n return 'listeners' in value;\n}\n\nfunction isPropertyHolder(value: SafeAny): value is Required<PropertyHolder> {\n return 'properties' in value;\n}\n\nfunction isObserverHolder(value: SafeAny): value is Required<EventObserverHolder> {\n return 'observers' in value;\n}\n\n/**\n * @internal\n */\n@Directive({\n selector: '[fluentBinding]',\n providers: [DestroyedSubject]\n})\nexport class FluentBindingDirective<E extends HTMLElement, C extends object, S extends AbstractSchema> implements OnInit {\n private readonly destroyRef = inject(DestroyRef);\n private readonly injector = inject(Injector);\n\n readonly fluentBinding = input.required<{ component?: C, schema: S, control: AbstractControl, model: AnyObject }>();\n\n private readonly schema = computed(() => this.fluentBinding().schema);\n private readonly model = computed(() => this.fluentBinding().model);\n private readonly control = computed(() => this.fluentBinding().control);\n private readonly component = computed(() => this.fluentBinding().component);\n\n constructor() {\n const elementRef: ElementRef<E> = inject(ElementRef);\n const destroyed = inject(DestroyedSubject);\n\n effect(() => {\n const component = this.component();\n const schema = this.schema();\n const control = this.control();\n const element = elementRef.nativeElement;\n const outputs = component\n ? reflectComponentType(component.constructor as Type<unknown>)?.outputs.map(output => output.propName) ?? []\n : [];\n\n untracked(() => {\n const context = (): ListenerContext => ({ control, schema, model: this.model(), element, component });\n destroyed.next();\n\n if (isPropertyHolder(schema)) {\n const host = component ?? element;\n\n for (const [property, value] of Object.entries(schema.properties)) {\n const prop = host[property as keyof (C | E)];\n if (isSignal(prop)) {\n signalSetFn(prop[SIGNAL] as SignalNode<SafeAny>, value);\n } else {\n host[property as keyof (C | E)] = value;\n }\n }\n }\n\n if (isListenerHolder(schema)) {\n for (const [eventName, listener] of Object.entries(schema.listeners)) {\n if (eventName === 'valueChange') {\n control.valueChanges.pipe(\n takeUntil(destroyed)\n ).subscribe(value => {\n listener!(value, context());\n });\n } else if (eventName === 'statusChange') {\n control.statusChanges.pipe(\n takeUntil(destroyed)\n ).subscribe(status => {\n listener!(status, context());\n });\n } else {\n // First try to bind to the component's output, then fallback to DOM event binding\n if (component) {\n if (outputs.includes(eventName)) {\n const output = component[eventName as keyof C] as Observable<SafeAny> | OutputRef<SafeAny>;\n const observable = output instanceof Observable ? output : outputToObservable(output);\n\n observable.pipe(\n takeUntil(destroyed)\n ).subscribe(event => {\n listener!(event, context());\n });\n\n continue;\n }\n }\n\n fromEvent(element, eventName).pipe(\n takeUntil(destroyed)\n ).subscribe(event => {\n listener!(event, context());\n });\n }\n }\n }\n\n if (isObserverHolder(schema)) {\n for (const [eventName, observer] of Object.entries(schema.observers)) {\n if (eventName === 'valueChange') {\n control.valueChanges.pipe(\n map(event => ({ event, context: context() })),\n observer!,\n takeUntil(destroyed)\n ).subscribe();\n } else if (eventName === 'statusChange') {\n control.statusChanges.pipe(\n map(event => ({ event, context: context() })),\n observer!,\n takeUntil(destroyed)\n