UNPKG

@zarlex/ngx-accessor

Version:

This library provides an adapter to interact with Angular forms. It also provides an adapter to work with object signals and forms

1 lines 119 kB
{"version":3,"file":"zarlex-ngx-accessor.mjs","sources":["../../../../../src/libs/ngx-accessor/src/lib/directives/accessors/checkbox-value-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/accessors/default-value-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/accessors/number-value-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/accessors/radio-control-value-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/accessors/range-value-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/accessors/select-control-value-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/accessors/select-multiple-control-value-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/utils.ts","../../../../../src/libs/ngx-accessor/src/lib/accessor/get-property.ts","../../../../../src/libs/ngx-accessor/src/lib/accessor/set-property.ts","../../../../../src/libs/ngx-accessor/src/lib/errors/validator-error.ts","../../../../../src/libs/ngx-accessor/src/lib/errors/validation-errors/max-length.ts","../../../../../src/libs/ngx-accessor/src/lib/errors/validation-errors/max.ts","../../../../../src/libs/ngx-accessor/src/lib/errors/validation-errors/min-length.ts","../../../../../src/libs/ngx-accessor/src/lib/errors/validation-errors/min.ts","../../../../../src/libs/ngx-accessor/src/lib/errors/validation-errors/required.ts","../../../../../src/libs/ngx-accessor/src/lib/errors/validation-errors/email.ts","../../../../../src/libs/ngx-accessor/src/lib/errors/validation-errors/invalid-nested-property.ts","../../../../../src/libs/ngx-accessor/src/lib/errors/validation-errors/pattern.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/validation.ts","../../../../../src/libs/ngx-accessor/src/lib/accessors/abstract-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/interface.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/validators/max.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/validators/max-length.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/validators/min.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/validators/min-length.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/validators/required.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/validators/email.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/validators/pattern.ts","../../../../../src/libs/ngx-accessor/src/lib/validation/value-validation.ts","../../../../../src/libs/ngx-accessor/src/lib/accessors/signal/signal-value-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/accessors/signal/utils/computed-previous.ts","../../../../../src/libs/ngx-accessor/src/lib/accessors/signal/signal-accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/accessor.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/validators/checkbox-required-validator.directive.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/validators/email-validator.directive.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/validators/max-length-validator.directive.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/validators/max-validator.directive.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/validators/min-length-validator.directive.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/validators/min-validator.directive.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/validators/pattern-validator.directive.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/validators/required-validator.directive.ts","../../../../../src/libs/ngx-accessor/src/lib/directives/module.ts","../../../../../src/libs/ngx-accessor/src/lib/type-utils/array.types.ts","../../../../../src/libs/ngx-accessor/src/lib/abstract-input.ts","../../../../../src/libs/ngx-accessor/src/lib/index.ts","../../../../../src/libs/ngx-accessor/src/zarlex-ngx-accessor.ts"],"sourcesContent":["import {Directive, forwardRef} from '@angular/core';\nimport {CheckboxControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';\n\n@Directive({\n selector: 'input[type=checkbox][ngxAccessor]',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => CheckboxValueAccessorDirective),\n multi: true\n }\n ],\n standalone: true\n})\nexport class CheckboxValueAccessorDirective extends CheckboxControlValueAccessor {\n}\n","import {Directive, forwardRef} from '@angular/core';\nimport {DefaultValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';\n\n@Directive({\n selector: 'input:not([type=checkbox])[ngxAccessor],textarea[ngxAccessor]',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => DefaultValueAccessorDirective),\n multi: true\n }\n ],\n standalone: true\n})\nexport class DefaultValueAccessorDirective extends DefaultValueAccessor {\n}\n","import {Directive, forwardRef} from '@angular/core';\nimport {NG_VALUE_ACCESSOR, NumberValueAccessor} from '@angular/forms';\n\n@Directive({\n selector: 'input[type=number][ngxAccessor]',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => NumberValueAccessorDirective),\n multi: true\n }\n ],\n standalone: true\n})\nexport class NumberValueAccessorDirective extends NumberValueAccessor {\n}\n","import {Directive, forwardRef} from '@angular/core';\nimport {NG_VALUE_ACCESSOR, RadioControlValueAccessor} from '@angular/forms';\n\n@Directive({\n selector: 'input[type=radio][ngxAccessor]',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => RadioControlValueAccessorDirective),\n multi: true\n }\n ],\n standalone: true\n})\nexport class RadioControlValueAccessorDirective extends RadioControlValueAccessor {\n}\n","import {Directive, forwardRef} from '@angular/core';\nimport {NG_VALUE_ACCESSOR, RangeValueAccessor} from '@angular/forms';\n\n@Directive({\n selector: 'input[type=range][ngxAccessor]',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => RangeValueAccessorDirective),\n multi: true\n }\n ],\n standalone: true\n})\nexport class RangeValueAccessorDirective extends RangeValueAccessor {\n}\n","import {Directive, forwardRef} from '@angular/core';\nimport {NG_VALUE_ACCESSOR, SelectControlValueAccessor} from '@angular/forms';\n\n@Directive({\n selector: 'select:not([multiple])[ngxAccessor]',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => SelectControlValueAccessorDirective),\n multi: true\n }\n ],\n standalone: true\n})\nexport class SelectControlValueAccessorDirective extends SelectControlValueAccessor {\n}\n","import {Directive, ElementRef, forwardRef, Host, Input, OnDestroy, Optional, Renderer2} from '@angular/core';\nimport { NG_VALUE_ACCESSOR, SelectMultipleControlValueAccessor } from '@angular/forms';\n\n@Directive({\n selector: 'select[multiple][ngxAccessor]',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => SelectMultipleControlValueAccessorDirective),\n multi: true\n }\n ],\n standalone: true\n})\nexport class SelectMultipleControlValueAccessorDirective extends SelectMultipleControlValueAccessor {\n}\n\nfunction _buildValueString(id: string, value: any): string {\n if (id == null) return `${value}`;\n if (typeof value === 'string') value = `'${value}'`;\n if (value && typeof value === 'object') value = 'Object';\n return `${id}: ${value}`.slice(0, 50);\n}\n\n/**\n * Adapted code from Angular https://github.com/angular/angular/blob/main/packages/forms/src/directives/select_multiple_control_value_accessor.ts\n * This code defines SelectMultipleControlValueAccessorDirective as select\n *\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n@Directive({\n selector: 'option',\n})\nexport class SelectMultipleOption implements OnDestroy {\n // TODO(issue/24571): remove '!'.\n id!: string;\n /** @internal */\n _value: any;\n\n constructor(\n private _element: ElementRef,\n private _renderer: Renderer2,\n @Optional() @Host() private _select: SelectMultipleControlValueAccessorDirective) {\n if (this._select) {\n // @ts-ignore\n this.id = this._select._registerOption(this);\n }\n }\n\n /**\n * @description\n * Tracks the value bound to the option element. Unlike the value binding,\n * ngValue supports binding to objects.\n */\n @Input('ngValue')\n set ngValue(value: any) {\n if (this._select == null) return;\n this._value = value;\n this._setElementValue(_buildValueString(this.id, value));\n this._select.writeValue(this._select.value);\n }\n\n /**\n * @description\n * Tracks simple string values bound to the option element.\n * For objects, use the `ngValue` input binding.\n */\n @Input('value')\n set value(value: any) {\n if (this._select) {\n this._value = value;\n this._setElementValue(_buildValueString(this.id, value));\n this._select.writeValue(this._select.value);\n } else {\n this._setElementValue(value);\n }\n }\n\n /** @internal */\n _setElementValue(value: string): void {\n this._renderer.setProperty(this._element.nativeElement, 'value', value);\n }\n\n /** @internal */\n _setSelected(selected: boolean) {\n this._renderer.setProperty(this._element.nativeElement, 'selected', selected);\n }\n\n /** @nodoc */\n ngOnDestroy(): void {\n if (this._select) {\n // @ts-ignore\n this._select._optionMap.delete(this.id);\n this._select.writeValue(this._select.value);\n }\n }\n}\n","import {AbstractControlDirective, ControlValueAccessor, NgControl} from '@angular/forms';\nimport { AccessorConfig, AccessorPropertyConfig } from './accessors/abstract-accessor';\nimport {ValidatorError} from './errors/validator-error';\nimport { SimplePath } from './type-utils';\nimport {IValidator} from './validation/interface';\nimport {\n CheckboxValueAccessorDirective,\n DefaultValueAccessorDirective,\n NumberValueAccessorDirective,\n RadioControlValueAccessorDirective,\n RangeValueAccessorDirective,\n SelectControlValueAccessorDirective,\n SelectMultipleControlValueAccessorDirective\n} from './directives/accessors';\n\nfunction _throwError(dir: AbstractControlDirective, message: string): void {\n let messageEnd: string;\n if (dir.path.length > 1) {\n messageEnd = `path: '${dir.path.join(' -> ')}'`;\n } else if (dir.path[0]) {\n messageEnd = `name: '${dir.path}'`;\n } else {\n messageEnd = 'unspecified name attribute';\n }\n throw new Error(`${message} ${messageEnd}`);\n}\n\nexport const BUILTIN_ACCESSORS = [\n CheckboxValueAccessorDirective,\n RangeValueAccessorDirective,\n NumberValueAccessorDirective,\n SelectControlValueAccessorDirective,\n SelectMultipleControlValueAccessorDirective,\n RadioControlValueAccessorDirective,\n];\n\n/**\n * Adapted code from https://github.com/angular/angular/blob/6789c7ef947952551d7598fe37a3d86093b75720/packages/forms/src/directives/shared.ts#L367\n *\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nexport function isBuiltInAccessor(valueAccessor: ControlValueAccessor): boolean {\n return BUILTIN_ACCESSORS.some(a => valueAccessor.constructor === a);\n}\n\n\n/**\n * Adapted code from https://github.com/angular/angular/blob/6789c7ef947952551d7598fe37a3d86093b75720/packages/forms/src/directives/shared.ts#L388\n *\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nexport function selectValueAccessor(\n dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor | null {\n if (!valueAccessors) {\n return null;\n }\n\n let defaultAccessor: ControlValueAccessor | undefined = undefined;\n let builtinAccessor: ControlValueAccessor | undefined = undefined;\n let customAccessor: ControlValueAccessor | undefined = undefined;\n\n valueAccessors.forEach((v: ControlValueAccessor) => {\n if (v.constructor === DefaultValueAccessorDirective) {\n defaultAccessor = v;\n } else if (isBuiltInAccessor(v)) {\n builtinAccessor = v;\n } else {\n customAccessor = v;\n }\n });\n\n if (customAccessor) {\n return customAccessor;\n }\n if (builtinAccessor) {\n return builtinAccessor;\n }\n if (defaultAccessor) {\n return defaultAccessor;\n }\n\n _throwError(dir, 'No valid value accessor for form control with');\n\n return null;\n}\n\nexport function getValidatorError(key: any, value: any, validator: IValidator): { [key: string]: ValidatorError } {\n const result: any = {};\n result[key] = validator.error(value);\n return result;\n}\n\nexport function keyValidatorsFromAccessorConfig<TProperties extends object>(config: AccessorConfig<TProperties>): { [key in SimplePath<TProperties>]?: Array<IValidator> } {\n return Object.entries(config || {}).reduce((previousValue, [key, propertyAccessorConfig]: [SimplePath<TProperties>, AccessorPropertyConfig]) => {\n if(propertyAccessorConfig?.validators?.length){\n previousValue[key] = propertyAccessorConfig.validators\n }\n return previousValue;\n }, {} as { [key in SimplePath<TProperties>]?: Array<IValidator> })\n}\n\n\nexport function isEqual(val1: unknown, val2: unknown): boolean {\n switch (typeof val1) {\n case 'number':\n case 'string':\n case 'boolean':\n return val1 === val2;\n default:\n return JSON.stringify(val1) === JSON.stringify(val2);\n }\n}\n","import {GetProperty} from '../type-utils/get.types';\n\nexport const getProperty: GetProperty = (object, path) => {\n if (path === undefined || path === null) {\n return object;\n }\n\n return path.toString().split('.').reduce((previous, current) => {\n if (previous && current.length) {\n return previous[current];\n } else {\n return previous;\n }\n }, object as any);\n};\n","/**\n * Set property of object by path in dot notation\n */\nimport {SetProperty} from '../type-utils/set.types';\n\nexport const setProperty: SetProperty = (obj, path, value) => {\n if (path === undefined || path === null) {\n return;\n }\n\n const pathArray = path.toString().split('.');\n pathArray.reduce(\n (previous, current, index) => {\n if (index === pathArray.length - 1) {\n if (current.length) {\n previous[current] = value;\n } else {\n Object.assign(previous, value);\n }\n } else {\n if (!previous || !previous[current]) {\n // Check if the next index is a number in order to determine whether empty object should be an object or array\n if (Number.isFinite(parseInt(pathArray[index + 1], 10))) {\n previous[current] = [];\n } else {\n previous[current] = {};\n }\n }\n }\n return previous[current];\n }, obj as any\n );\n};\n","export class ValidatorError<TParams = any> extends Error {\n public override message: string;\n public params: TParams;\n public id: string;\n\n constructor(options: { id: string, message: string, params: TParams }) {\n super(options.message);\n this.id = options.id;\n this.message = options.message;\n this.params = options.params;\n }\n}\n","import {ValidatorError} from '../validator-error';\n\nexport class MaxLengthValidationError extends ValidatorError<{ maxLength: number, currentLength: number }> {\n constructor(params: { maxLength: number, currentLength: number }) {\n super({\n params,\n id: 'maxlength',\n message: `Invalid length! Has to be smaller than ${params.maxLength}. Current length is ${params.currentLength}`\n });\n }\n}\n","import {ValidatorError} from '../validator-error';\n\nexport class MaxValidationError extends ValidatorError<{ max: number, current: number }> {\n constructor(params: { max: number, current: number }) {\n super({\n params,\n id: 'max',\n message: `Invalid number! Has to be smaller than ${params.max}. Current number is ${params.current}`\n });\n }\n}\n","import {ValidatorError} from '../validator-error';\n\nexport class MinLengthValidationError extends ValidatorError<{ minLength: number, currentLength: number }> {\n constructor(params: { minLength: number, currentLength: number }) {\n super({\n params,\n id: 'minlength',\n message: `Invalid length! Has to be larger than ${params.minLength}. Current length is ${params.currentLength}`\n });\n }\n}\n","import {ValidatorError} from '../validator-error';\n\nexport class MinValidationError extends ValidatorError< { min: number, current: number }> {\n constructor(params: { min: number, current: number }) {\n super({\n params,\n id: 'min',\n message: `Invalid number! Has to be bigger than ${params.min}. Current number is ${params.current}`\n });\n }\n}\n","import {ValidatorError} from '../validator-error';\n\nexport class RequiredValidationError extends ValidatorError {\n constructor() {\n super({\n params: {},\n id: 'required',\n message: `Required field`\n });\n }\n}\n","import {ValidatorError} from '../validator-error';\n\nexport class EmailValidationError extends ValidatorError<{ value: string }> {\n constructor(params: { value: string }) {\n super({\n id: 'email',\n message: `Not an e-mail! ${params.value} seems not to be a valid e-mail address`,\n params\n });\n }\n}\n","import { ValidatorError } from '../validator-error';\n\nexport class InvalidNestedPropertyError extends ValidatorError<{\n errors: { [key: string]: Array<ValidatorError> }\n}> {\n constructor(errors: { [key: string]: Array<ValidatorError> }) {\n super({\n params: {errors},\n id: 'invalidNestedProperty',\n message: `${Object.keys(errors).length} invalid properties`\n });\n }\n}\n","import {ValidatorError} from '../validator-error';\n\nexport class PatternValidationError extends ValidatorError<{ value: string, pattern: RegExp }> {\n constructor(params: { value: string, pattern: RegExp }) {\n super({\n id: 'pattern',\n message: `Does not match pattern! ${params.value} does not match the required pattern ${params.pattern.toString()}`,\n params\n });\n }\n}\n","import { Signal, signal, WritableSignal } from '@angular/core';\nimport { InvalidNestedPropertyError, ValidatorError } from '../errors';\nimport { SimplePath } from '../type-utils';\nimport { IValidator, ValidationUpdate } from \"./interface\";\n\nexport class Validation<TProperties extends object> {\n public readonly isValid: Signal<boolean>;\n public readonly errors: Signal<Array<ValidatorError>>;\n public isValidating: Signal<boolean>;\n\n protected errorMap: Map<string, ValidationUpdate> = new Map();\n protected writableErrors: WritableSignal<Array<ValidatorError>> = signal([]);\n protected writableIsValid: WritableSignal<boolean> = signal(false);\n protected writableIsValidating: WritableSignal<boolean> = signal(false);\n\n constructor(public readonly validators: { [key in SimplePath<TProperties>]?: Array<IValidator> }) {\n this.errors = this.writableErrors.asReadonly();\n this.isValid = this.writableIsValid.asReadonly();\n this.isValidating = this.writableIsValidating.asReadonly();\n }\n\n public update(key: string, update: ValidationUpdate): void {\n if (!update.isValid) {\n this.errorMap.set(key, update);\n } else {\n this.errorMap.delete(key);\n }\n\n const updates = Array.from(this.errorMap.values());\n const nestedErrors: { [key: string]: Array<ValidatorError> } = Array\n .from(this.errorMap.entries())\n .reduce((previous, [key, value]) => {\n if (value.errors.length) {\n previous[key] = value.errors\n }\n return previous;\n }, {})\n if (Object.keys(nestedErrors).length) {\n this.writableErrors.set([\n new InvalidNestedPropertyError(nestedErrors),\n ])\n } else {\n this.writableErrors.set([]);\n }\n this.writableIsValidating.set(updates.find(update => update.isValidating) !== undefined);\n if(updates.find(update => !update.isValid)){\n this.writableIsValid.set(false);\n } else {\n this.writableIsValid.set(true);\n }\n }\n}\n","import { Signal, signal, WritableSignal } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { Accessor } from '../accessor/interface';\nimport { ArrayAccessors, IsValidAccessorKey } from '../type-utils/accessor.types';\nimport { GetPropertyType, SimplePath } from '../type-utils/get.types';\nimport { keyValidatorsFromAccessorConfig } from '../utils';\nimport { IValidator, ValidationUpdate } from '../validation/interface';\nimport { Validation } from '../validation/validation';\n\nexport interface AccessorCreateOptions {\n isEqual: (val1: unknown, val2: unknown) => boolean;\n validators: { [key: string]: Array<IValidator> },\n onValidationChange: (update: ValidationUpdate) => void;\n}\n\nexport interface IAccessorOptions<TProperties extends object> {\n validators: { [key in SimplePath<TProperties>]?: Array<IValidator> };\n}\n\nexport interface AccessorPropertyConfig {\n validators?: Array<IValidator>;\n isEqual?:(val1: unknown, val2: unknown) => boolean;\n}\n\nexport type AccessorConfig<TProperties extends object> = { [property in SimplePath<TProperties>]?: AccessorPropertyConfig };\n\nexport abstract class AbstractAccessor<TProperties extends object> {\n public readonly validation: Validation<TProperties>;\n public readonly isDirty: Signal<boolean>;\n protected dirtyKeys: Map<string, boolean> = new Map<string, boolean>();\n protected writableIsDirty: WritableSignal<boolean> = signal(false);\n protected accessorMap: Map<string, Accessor<any, any>> = new Map();\n protected accessorSubscriptionsMap: Map<string, Subscription> = new Map();\n\n constructor(public config: AccessorConfig<TProperties> = {}) {\n this.validation = new Validation<TProperties>(keyValidatorsFromAccessorConfig(config));\n this.isDirty = this.writableIsDirty.asReadonly();\n }\n\n public access<TKey extends string | undefined = undefined>(\n key?: IsValidAccessorKey<TKey, TProperties>\n ): [TKey] extends [undefined] ? Accessor<TProperties, TProperties> : Accessor<TProperties, GetPropertyType<TProperties, TKey>> {\n if (key) {\n return this.accessProperty(key);\n } else {\n return this.accessRoot() as any;\n }\n }\n\n public getAccessors(): ArrayAccessors<any> {\n return this.access().getAccessors() as ArrayAccessors<any>;\n }\n\n public removeAccess<TKey extends string>(\n key: IsValidAccessorKey<TKey, TProperties>\n ): void {\n this.accessorSubscriptionsMap.get(key as string)?.unsubscribe();\n this.accessorMap.delete(key as string);\n this.accessorSubscriptionsMap.delete(key as string);\n }\n\n public destroy(): void {\n Array.from(this.accessorMap.values()).forEach(accesor => accesor.destroy());\n Array.from(this.accessorSubscriptionsMap.values()).forEach(subscription => subscription.unsubscribe());\n this.accessorMap.clear();\n this.accessorSubscriptionsMap.clear();\n }\n\n public setPristine(): void {\n this.writableIsDirty.set(false);\n this.accessorMap.forEach((accessor) => accessor.setPristine());\n }\n\n protected accessRoot(): Accessor<TProperties, TProperties> {\n let existing: Accessor<any, any> = this.accessorMap.get('root');\n if (!existing) {\n existing = this.createRootAccessor({\n isEqual: this.config['']?.isEqual,\n validators: this.validation.validators,\n onValidationChange: (update) => {\n this.validation.update('', update);\n }\n });\n this.accessorMap.set('root', existing);\n this.accessorSubscriptionsMap.set('root', new Subscription());\n }\n return existing;\n }\n\n protected accessProperty<TKey extends string>(\n key: IsValidAccessorKey<TKey, TProperties>\n ): Accessor<TProperties, GetPropertyType<TProperties, TKey>> {\n let existing: Accessor<any, any> = this.accessorMap.get(key as string);\n if (!existing) {\n existing = this.createAccessor(key, {\n isEqual: this.config[key as string]?.isEqual,\n validators: this.validation.validators,\n onValidationChange: (update) => {\n this.validation.update(key as string, update);\n },\n });\n this.accessorMap.set(key as string, existing);\n this.accessorSubscriptionsMap.set(key as string, new Subscription());\n\n }\n return existing;\n }\n\n protected abstract createAccessor<TKey extends keyof TProperties>(key: TKey, options: AccessorCreateOptions): Accessor<any, any>;\n\n protected abstract createRootAccessor(options: AccessorCreateOptions): Accessor<any, any>;\n}\n","import { Observable } from 'rxjs';\nimport { ValidatorError } from '../errors/validator-error';\n\nexport type ValidatorFn = (value: any) => (boolean);\nexport type AsyncValidatorFn = (value: any) => (Promise<boolean> | Observable<boolean>);\n\nexport enum ValidatorId{\n required = 'required',\n minLength = 'minLength',\n maxLength = 'maxLength',\n min = 'min',\n max = 'max',\n pattern = 'pattern',\n email = 'email',\n}\n\nexport interface IValidator {\n id?: string;\n isValid: AsyncValidatorFn | ValidatorFn,\n async: boolean;\n error: (value: any) => ValidatorError;\n setupElement?: (el: HTMLElement) => void;\n}\n\nexport type ValidationErrors<TProperties> = {\n [key in keyof TProperties]: Array<ValidatorError>;\n};\n\nexport interface ValidationUpdate{\n isValid: boolean;\n isValidating: boolean;\n errors: Array<ValidatorError>;\n}\n","import { MaxValidationError } from '../../errors/validation-errors/max';\nimport { IValidator, ValidatorId } from '../interface';\n\nexport function maxValidator(max: number): IValidator {\n return {\n id: ValidatorId.max,\n isValid: (value) => {\n return !Number.isFinite(value) || value <= max;\n },\n async: false,\n error: (value) => new MaxValidationError({\n max: max,\n current: value\n }),\n setupElement: (el: HTMLInputElement) => {\n el.setAttribute('max', max.toString());\n }\n }\n}\n\n","import { MaxLengthValidationError } from '../../errors/validation-errors/max-length';\nimport { IValidator, ValidatorId } from '../interface';\n\nexport function maxLengthValidator(maxLength: number): IValidator {\n return {\n id: ValidatorId.maxLength,\n isValid: (value) => {\n return !value || value && value.length <= maxLength;\n },\n async: false,\n error: (value) => new MaxLengthValidationError({maxLength: maxLength, currentLength: value.length}),\n setupElement: (el: HTMLElement) => el.setAttribute('maxLength', maxLength.toString())\n }\n}\n\n","import { MinValidationError } from '../../errors/validation-errors/min';\nimport { IValidator, ValidatorId } from '../interface';\n\nexport function minValidator(min: number): IValidator {\n return {\n id: ValidatorId.min,\n isValid: (value) => {\n return !Number.isFinite(value) || value >= min;\n },\n async: false,\n error: (value) => {\n return new MinValidationError({\n min: min,\n current: value\n })\n },\n setupElement: (el: HTMLInputElement) => {\n el.setAttribute('min', min.toString());\n }\n }\n}\n\n","import { MinLengthValidationError } from '../../errors/validation-errors/min-length';\nimport { IValidator, ValidatorId } from '../interface';\n\nexport function minLengthValidator(minLength: number): IValidator {\n return {\n id: ValidatorId.minLength,\n isValid: (value) => {\n return !value || value.length >= minLength;\n },\n async: false,\n error: (value) => new MinLengthValidationError({\n minLength: minLength,\n currentLength: value.length\n }),\n setupElement: (el: HTMLElement) => el.setAttribute('minLength', minLength.toString())\n }\n}\n\n","import { RequiredValidationError } from '../../errors/validation-errors/required';\nimport { IValidator, ValidatorId } from '../interface';\n\nexport function requiredValidator(): IValidator {\n return {\n id: ValidatorId.required,\n isValid: (value) => {\n let valid;\n switch (typeof value) {\n case 'boolean':\n valid = value === true;\n break;\n case 'number':\n valid = Number.isFinite(value);\n break;\n case 'string':\n valid = value && value.length > 0;\n break;\n default:\n valid = !!value;\n }\n return valid;\n },\n async: false,\n error: (value) => new RequiredValidationError(),\n setupElement: (el: HTMLInputElement) => {\n el.setAttribute('required', 'required');\n }\n }\n}\n\n","import { EmailValidationError } from '../../errors/validation-errors/email';\nimport { IValidator, ValidatorId } from '../interface';\n\nconst EMAIL_REGEXP =\n /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\n\nexport function emailValidator(): IValidator {\n return {\n id: ValidatorId.email,\n isValid: (value) => {\n return !value || EMAIL_REGEXP.test(value)\n },\n async: false,\n error: (value) => new EmailValidationError({value: value})\n }\n}\n\n","import { PatternValidationError } from '../../errors/validation-errors/pattern';\nimport { IValidator, ValidatorId } from '../interface';\n\nexport function patternValidator(pattern: string | RegExp): IValidator {\n let regex: RegExp;\n let regexStr: string;\n if (typeof pattern === 'string') {\n regexStr = '';\n if (pattern.charAt(0) !== '^') {\n regexStr += '^';\n }\n regexStr += pattern;\n if (pattern.charAt(pattern.length - 1) !== '$') {\n regexStr += '$';\n }\n regex = new RegExp(regexStr);\n } else {\n regex = pattern;\n }\n return {\n id: ValidatorId.pattern,\n isValid: (value) => {\n if (!value) {\n return true;\n }\n return regex.test(value);\n },\n async: false,\n error: (value) => new PatternValidationError({value: value, pattern: regex}),\n setupElement: (el: HTMLInputElement) => {\n el.setAttribute('pattern', pattern.toString());\n }\n }\n}\n\n","import { computed, Signal, signal, WritableSignal } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { ValidatorError } from '../errors/validator-error';\nimport { IValidator, ValidationUpdate } from \"./interface\";\n\nexport class ValueValidation<TValue> {\n public isValidating: Signal<boolean>;\n public errors: Signal<Array<ValidatorError>>;\n public isValid: Signal<boolean> = computed(() => {\n return !this.isValidating() && this.errors().length === 0;\n })\n public update: Subject<ValidationUpdate> = new Subject();\n public nestedErrors: Map<string, Array<ValidatorError>> = new Map();\n public nestedValidating: Map<string, boolean> = new Map();\n protected writableIsValidating: WritableSignal<boolean> = signal(false);\n protected writableErrors: WritableSignal<Array<ValidatorError>> = signal([]);\n\n constructor(public readonly validators: Array<IValidator>) {\n this.isValidating = this.writableIsValidating.asReadonly();\n this.errors = this.writableErrors.asReadonly();\n }\n\n public setErrors(errors: ValidatorError[]): void {\n this.writableErrors.set(errors);\n this.update.next({\n isValid: this.isValid(),\n errors: errors,\n isValidating: this.isValidating()\n })\n }\n\n public setIsValidating(isValidating: boolean): void {\n if (this.isValidating() !== isValidating) {\n this.writableIsValidating.set(isValidating);\n this.update.next({\n isValid: this.isValid(),\n errors: this.errors(),\n isValidating: isValidating\n });\n }\n }\n}\n","import { computed, signal, Signal, WritableSignal } from '@angular/core';\nimport { filter, map, Observable } from 'rxjs';\nimport { getProperty } from '../../accessor/get-property';\nimport { Accessor } from '../../accessor/interface';\nimport { setProperty } from '../../accessor/set-property';\nimport { InvalidNestedPropertyError } from '../../errors';\nimport { ValidatorError } from '../../errors/validator-error';\nimport { ArrayAccessors, ArrayItemAccessor } from '../../type-utils';\nimport { GetPropertyType } from '../../type-utils/get.types';\nimport { isEqual } from '../../utils';\nimport { IValidator, ValidationUpdate, ValidatorId } from '../../validation';\nimport { ValueValidation } from '../../validation/value-validation';\nimport { SignalProperties } from './interface';\n\nexport class SignalValueAccessor<\n TSignal extends WritableSignal<any>,\n TProperties extends SignalProperties<TSignal>,\n TProperty extends keyof TProperties\n> implements Accessor<TProperties, TProperties[TProperty]> {\n public readonly id: string = Math.random().toString(36).substr(2);\n public readonly isDirty: WritableSignal<boolean> = signal(false);\n public readonly validation: ValueValidation<TProperties[TProperty]>;\n public readonly isRequired: Signal<boolean> = computed(() => {\n return this.validation.validators?.find(validator => validator.id === ValidatorId.required) !== undefined;\n });\n public readonly arrayAccessors: ArrayAccessors<TProperties[TProperty]> = [] as ArrayAccessors<TProperties[TProperty]>;\n public readonly get: Signal<TProperties[TProperty]>;\n public readonly errors: WritableSignal<Array<ValidatorError>> = signal([]);\n\n protected readonly accessorMap: Map<string, Accessor<any, any>> = new Map();\n protected readonly dirtyMap: Map<string, boolean> = new Map();\n\n constructor(\n protected signalValue: TSignal,\n public property: TProperty,\n public options: {\n validators: { [key: string]: Array<IValidator> },\n parentKeys: Array<string>,\n onDestroy: () => void,\n onIsDirty: (isDirty: boolean) => void,\n onValidationChange: (update: ValidationUpdate) => void,\n observable: Observable<{ new: TProperties[TProperty], previous: TProperties[TProperty] }>,\n isEqual: (val1: unknown, val2: unknown) => boolean\n },\n ) {\n this.validation = new ValueValidation(options.validators[this.getValidatorKey()]);\n this.validation.update.subscribe(update => {\n options.onValidationChange(update);\n });\n this.setArrayAccessors();\n this\n .get$()\n .subscribe((newValue) => {\n if (Array.isArray(newValue) && this.arrayAccessorsHaveChanged()) {\n this.setArrayAccessors();\n this.updateValidation();\n }\n });\n this.get = computed(() => {\n return this.getValue();\n })\n }\n\n getAccessors(): TProperties[TProperty] extends Array<any> ? Array<Accessor<TProperties[TProperty], GetPropertyType<TProperties[TProperty], '0'>>> : never {\n return this.arrayAccessors as any;\n }\n\n getAccessKey(): string {\n return this.options.parentKeys.length ?\n `${this.options.parentKeys.join('.')}.${this.property as string}` : this.property as string;\n }\n\n getValidatorKey(): string {\n const parentKeys = this.options.parentKeys.filter(key => typeof key !== 'number');\n return parentKeys.length ?\n `${parentKeys.join('.')}.${this.property as string}` : this.property as string;\n }\n\n get$(): Observable<TProperties[TProperty]> {\n return this.options.observable.pipe(\n filter(change => {\n const key: string = this.getAccessKey();\n return !this.isEqual(getProperty(change.new, key), getProperty(change.previous, key));\n }),\n map(() => this.getValue())\n );\n }\n\n set(value: TProperties[TProperty]): void {\n const key: string = this.getAccessKey();\n this.signalValue.update(state => {\n if (key !== undefined && key !== null) {\n const clone = structuredClone(state || {});\n setProperty(clone, key as any, value);\n return clone;\n } else {\n return value;\n }\n });\n }\n\n access<Property extends keyof TProperties[TProperty]>(\n key: Property | number\n ): Accessor<TProperties[TProperty], TProperties[TProperty][Property]> {\n let existing: Accessor<any, any> = this.accessorMap.get(key as string);\n if (!existing) {\n existing = this.createItemAccessor(key as string);\n this.accessorMap.set(key as string, existing);\n }\n return existing as Accessor<any, any>;\n }\n\n setDirty(isDirty: boolean): void {\n this.isDirty.set(isDirty);\n this.options.onIsDirty(isDirty);\n }\n\n setPristine(): void {\n this.isDirty.set(false);\n for (const entry of this.accessorMap.values()) {\n entry.setPristine();\n }\n this.arrayAccessors.forEach(accessor => {\n accessor.setPristine();\n });\n }\n\n destroy(): void {\n Array.from(this.accessorMap.values()).forEach(accesor => accesor.destroy());\n this.accessorMap.clear();\n this.validation.setErrors([]);\n this.validation.setIsValidating(false);\n this.options.onDestroy();\n }\n\n public getValue(): TProperties[TProperty] {\n const key: string = this.getAccessKey();\n if (key !== undefined && key !== null) {\n return getProperty(this.signalValue(), key);\n } else {\n return this.signalValue();\n }\n }\n\n protected setArrayAccessors(): void {\n if (Array.isArray(this.getValue())) {\n const items: Array<any> = this.getValue();\n\n if (this.arrayAccessors.length > items.length) {\n const findDeleteIndexes = this.arrayAccessors.reduce((previousValue, currentValue, currentIndex, array) => {\n if (this.findItemIndexForAccessor(currentValue) === -1) {\n previousValue.push(currentIndex);\n }\n return previousValue;\n }, []);\n\n findDeleteIndexes.sort().reverse().forEach(deleteIndex => {\n this.arrayAccessors[deleteIndex].destroy();\n this.arrayAccessors.splice(deleteIndex, 1);\n });\n }\n\n items.forEach((item, index) => {\n const accessorIndex = this.findItemAccessorIndex(item);\n if (accessorIndex === -1) {\n this.arrayAccessors[index] = this.createItemAccessor(index);\n } else if (accessorIndex !== index) {\n if (!this.arrayAccessors[index] || this.isEqual(items[index], this.arrayAccessors[index].getValue())) {\n this.arrayAccessors[index] = this.createItemAccessor(index);\n } else {\n const accessor = this.arrayAccessors[accessorIndex];\n this.arrayAccessors[accessorIndex] = this.arrayAccessors[index];\n this.arrayAccessors[index] = accessor;\n }\n }\n });\n }\n }\n\n protected updateValidation(): void {\n let errors = [];\n if (this.validation.nestedErrors.get('')?.length) {\n errors = errors.concat(this.validation.nestedErrors.get(''));\n }\n const nestedErrors = Array\n .from(this.validation.nestedErrors)\n .filter(([key, value]) => key !== '' && value?.length > 0)\n .reduce((previous, current) => {\n previous[current[0]] = current[1];\n return previous;\n }, {});\n if (Object.keys(nestedErrors).length > 0) {\n errors.push(new InvalidNestedPropertyError(nestedErrors));\n }\n this.validation.setErrors(errors);\n\n const nestedValidating: boolean = Array.from(this.validation.nestedValidating.values()).find(value => value === true);\n this.validation.setIsValidating(nestedValidating);\n }\n\n protected arrayAccessorsHaveChanged(): boolean {\n const newValue = this.getValue() as Array<any>;\n let changed = false;\n if (this.arrayAccessors.length !== (newValue as Array<any>)?.length) {\n changed = true;\n } else {\n this.arrayAccessors.forEach((accessor, index) => {\n if (!changed) {\n changed = accessor.getValue() !== newValue[index];\n }\n });\n }\n return changed;\n }\n\n protected createItemAccessor(key: number | string): Accessor<TSignal, TProperties> {\n return new SignalValueAccessor(this.signalValue, key as any, {\n ...this.options,\n parentKeys: (this.property !== undefined && this.property !== null)\n ? this.options.parentKeys.concat(this.property as any) : this.options.parentKeys,\n onValidationChange: (update) => {\n this.validation.nestedErrors.set(key.toString(), update.errors);\n this.validation.nestedValidating.set(key.toString(), update.isValidating);\n this.updateValidation();\n },\n onDestroy: () => {\n this.dirtyMap.delete(key as string);\n this.validation.nestedErrors.set(key.toString(), []);\n this.validation.nestedValidating.set(key.toString(), false);\n this.validation.setErrors([]);\n this.validation.setIsValidating(false);\n this.accessorMap.delete(key as string);\n if(this.accessorMap.size === 0 && this.arrayAccessors.length === 0){\n this.destroy();\n }\n },\n onIsDirty: (isDirty: boolean) => {\n this.dirtyMap.set(key as string, isDirty);\n const isSomethingDirty = Array.from(this.dirtyMap.values()).find(isDirty => isDirty);\n if (this.isDirty() !== isSomethingDirty) {\n this.setDirty(isSomethingDirty);\n }\n }\n });\n }\n\n protected findItemAccessorIndex(item: any): number {\n return this\n .arrayAccessors\n .findIndex(accessor => accessor && this.isEqual(accessor.getValue(), item));\n }\n\n protected findItemIndexForAccessor(accessor: ArrayItemAccessor<TProperties>): number {\n const items: Array<any> = this.getValue();\n return items\n .findIndex(item => this.isEqual(accessor.getValue(), item));\n }\n\n protected isEqual(val1: unknown, val2: unknown): boolean {\n return typeof this.options.isEqual === 'function' ? this.options.isEqual(val1, val2) : isEqual(val1, val2);\n }\n}\n","import {computed, Signal, untracked} from '@angular/core';\n\nexport function computedPrevious<T>(s: Signal<T>): Signal<T> {\n let current = null as T;\n let previous = untracked(() => s()); // initial value is the current value\n\n return computed(() => {\n current = s();\n const result = previous;\n previous = structuredClone(current);\n return result;\n });\n}\n","import { effect, signal, Signal, WritableSignal } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\nimport { Accessor } from '../../accessor/interface';\nimport { ArrayAccessors } from '../../type-utils';\nimport { AbstractAccessor, AccessorConfig, AccessorCreateOptions } from '../abstract-accessor';\nimport { SignalProperties } from './interface';\nimport { SignalValueAccessor } from './signal-value-accessor';\nimport { computedPrevious } from './utils/computed-previous';\n\n\nexport class SignalAccessor<TSignal extends WritableSignal<any> = WritableSignal<any>>\n extends AbstractAccessor<SignalProperties<TSignal>> {\n public arrayAccessors: WritableSignal<Array<Accessor<SignalProperties<TSignal>, SignalProperties<TSignal>>>>;\n protected signalObservable: Subject<{ new: SignalProperties<TSignal>, previous: SignalProperties<TSignal> }>;\n protected previous: Signal<SignalProperties<TSignal>>;\n\n constructor(\n private valueSignal: TSignal,\n options?: AccessorConfig<SignalProperties<TSignal>>\n ) {\n super(options);\n this.arrayAccessors = signal([]);\n this.previous = computedPrevious(this.valueSignal);\n effect(() => {\n const newValue: SignalProperties<TSignal> = this.valueSignal();\n const previousValue: SignalProperties<TSignal> = this.previous();\n this.signalObservable.next({\n new: newValue,\n previous: previousValue\n });\n this.arrayAccessors.set(this.access().getAccessors());\n });\n\n this.signalObservable = new Subject();\n }\n\n public override getAccessors(): ArrayAccessors<any> {\n return this.access().getAccessors() as ArrayAccessors<any>;\n }\n\n protected createAccessor<TKey extends keyof SignalProperties<TSignal>>(\n key: TKey,\n options: AccessorCreateOptions\n ): Accessor<SignalProperties<TSignal>, SignalProperties<TSignal>[TKey]> {\n return new SignalValueAccessor(this.valueSignal, key, {\n isEqual: options.isEqual,\n validators: options.validators,\n onValidationChange: (update) => {\n options.onValidationChange(update);\n },\n onIsDirty: (isDirty) => {\n this.dirtyKeys.set(key as string, isDirty);\n const somethingIsDirty = Array.from(this.dirtyKeys.values()).find(isDirty => isDirty);\n this.writableIsDirty.set(somethingIsDirty);\n },\n onDestroy: () => {\n this.accessorMap.delete(key as string);\n this.accessorSubscriptionsMap.get(key as string)?.unsubscribe();\n this.accessorSubscriptionsMap.delete(key as string);\n this.dirtyKeys.delete(key as string);\n },\n parentKeys: [],\n observable: this.signalObservable\n });\n }\n\n protected override createRootAccessor(\n options: AccessorCreateOptions\n ): Accessor<SignalProperties<TSignal>, any> {\n return new SignalValueAccessor(this.valueSignal, undefined, {\n isEqual: options.isEqual,\n validators: {},\n onValidationChange: (update) => {\n options?.onValidationChange(update);\n },\n onIsDirty: (isDirty) => {\n this.dirtyKeys.set('', isDirty);\n const somethingIsDirty = Array.from(this.dirtyKeys.values()).find(isDirty => isDirty);\n this.writableIsDirty.set(somethingIsDirty);\n },\n onDestroy: () => {\n this.accessorMap.delete('');\n this.accessorSubscriptionsMap.get('')?.unsubscribe();\n this.accessorSubscriptionsMap.delete('');\n this.dirtyKeys.delete('');\n },\n parentKeys: [],\n observable: this.signalObservable\n });\n }\n}\n","import {\n AfterViewInit,\n Directive,\n effect,\n ElementRef,\n forwardRef,\n inject,\n Inject, Injectable,\n InjectionToken,\n input,\n InputSignal,\n OnDestroy,\n Optional,\n output,\n OutputEmitterRef,\n Self, signal, WritableSignal\n} from '@angular/core';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport {\n AsyncValidator,\n AsyncValidatorFn,\n ControlValueAccessor,\n FormControlStatus,\n NG_ASYNC_VALIDATORS,\n NG_VALIDATORS,\n NG_VALUE_ACCESSOR,\n NgControl,\n NgModel, PristineChangeEvent,\n UntypedFormControl,\n Validator,\n ValidatorFn\n} from '@angular/forms';\nimport { firstValueFrom, Observable, Subscription } from 'rxjs';\nimport { Accessor } from '../accessor/interface';\nimport { ValidatorError } from '../errors';\nimport { selectValueAccessor } from '../utils';\n\nexport interface AccessorHost {\n accessor: InputSignal<Accessor<any, any>>;\n required: InputSignal<boolean>;\n}\n\nexport const ngxAccessorHost = new InjectionToken<AccessorHost>('AccessorHost');\n\nexport type AccessorValueType<TAccessor extends Accessor<any, any>> = TAccessor extends Accessor<any, infer TValueType> ? TValueType : any;\n\nexport const ACCESSOR = new InjectionToken<AccessorService>('Accessor');\n\n@Injectable()\nexport class AccessorService{\n accessor: WritableSignal<Accessor<any, any>> = signal(undefined);\n}\n\n@Directive({\n selector: '[ngxAccessor]',\n providers: [\n {\n provide: NgControl,\n useExisting: forwardRef(() => AccessorDirective)\n },\n {\n provide: ngxAccessorHost,\n useExisting: forwardRef(() => AccessorDirective)\n },\n {\n provide: ACCESSOR,\n useClass: AccessorService,\n multi: false\n }\n ],\n standalone: false\n})\nexport class AccessorDirective<TAccessor extends Accessor<any, any>, TValue = AccessorValueType<TAccessor>> extends NgModel implements OnDestroy, AfterViewInit, AccessorHost {\n public valueChange: OutputEmitterRef<TValue> = output({alias: 'ngxAccessorValueChange'});\n protected accessorValueChange: OutputEmitterRef<any> = output();\n protected element: ElementRef = inject(ElementRef, {optional: true});\n private subscriptions: Subscription = new Subscription();\n public accessor: InputSignal<TAccessor> = input.required({alias: 'ngxAccessor'});\n public required: InputSignal<boolean> = input();\n public override readonly control: UntypedFormControl = new UntypedFormControl();\n public accessorService: AccessorService = inject(ACCESSOR);\n\n constructor(\n @Optional() @Self() @Inject(NG_VALIDATORS) validators: (Validator | ValidatorFn)[],\n @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:\n (AsyncValidator | AsyncValidatorFn)[],