@rytrox/form-signals
Version:
Simple Reimplementation of [Angular Forms](https://www.npmjs.com/package/@angular/forms) in Signals. Focused on simplicity, with full [Angular Material](https://www.npmjs.com/package/@angular/material) support.
1 lines • 80.3 kB
Source Map (JSON)
{"version":3,"file":"rytrox-form-signals.mjs","sources":["../../../projects/form-signals/src/lib/models/form.ts","../../../projects/form-signals/src/lib/models/form-control.ts","../../../projects/form-signals/src/lib/models/form-array.ts","../../../projects/form-signals/src/lib/models/form-group.ts","../../../projects/form-signals/src/lib/directives/abstract-form-directive.ts","../../../projects/form-signals/src/lib/directives/input/input-text.directive.ts","../../../projects/form-signals/src/lib/directives/input/input-number.directive.ts","../../../projects/form-signals/src/lib/directives/material/mat-checkbox.directive.ts","../../../projects/form-signals/src/lib/directives/input/input-checkbox.directive.ts","../../../projects/form-signals/src/lib/directives/input/input-date.directive.ts","../../../projects/form-signals/src/lib/directives/input/input-radio.directive.ts","../../../projects/form-signals/src/lib/directives/material/mat-radio-group.directive.ts","../../../projects/form-signals/src/lib/directives/input/input-range.directive.ts","../../../projects/form-signals/src/lib/directives/input/input-file.directive.ts","../../../projects/form-signals/src/lib/directives/select/select.directive.ts","../../../projects/form-signals/src/lib/directives/material/mat-slider-thumb.directive.ts","../../../projects/form-signals/src/lib/directives/material/mat-slide-toggle.directive.ts","../../../projects/form-signals/src/lib/directives/material/mat-select.directive.ts","../../../projects/form-signals/src/lib/directives/material/mat-button-toggle-group.directive.ts","../../../projects/form-signals/src/lib/directives/material/mat-slider-range-thumb.directive.ts","../../../projects/form-signals/src/lib/directives/material/mat-datepicker.directive.ts","../../../projects/form-signals/src/lib/forms.module.ts","../../../projects/form-signals/src/public-api.ts","../../../projects/form-signals/src/rytrox-form-signals.ts"],"sourcesContent":["import {ValidatorFn} from \"./validator\";\nimport {signal, Signal, WritableSignal} from \"@angular/core\";\n\n/**\n * Resolves any factory function inside your Code and returns the Type of the generated Form.\n */\nexport type FormFactoryType<F> = (\n F extends (val?: any) => infer FG ? FG : never\n);\n\n\n/**\n * Abstract Interface of a Form.\n *\n * Every Form is a Signal, that can hold and set values.\n * Additionally, there are Signals inside to check states or validators\n */\nexport interface Form<T, E> extends WritableSignal<T> {\n\n /**\n * The current value of errors.\n * null is considered as VALID!\n */\n readonly errors: Signal<E | null>;\n\n /**\n * Holds all registered validators of the form\n */\n readonly validators: Signal<ValidatorFn<T>[]>;\n\n /**\n * Adds some validators to this form.\n * You can't add the same validator twice, this will be ignored by its implementation.\n *\n * After adding, the form will be validated again\n *\n * @param validators the validators you want to add\n */\n addValidators(...validators: ValidatorFn<T>[]): void;\n\n /**\n * Removes some validators from this form.\n * If you try to remove a validator that doesn't exist in this form, it will be ignored\n *\n * After removing, the form will be validated again\n *\n * @param validators the validators you want to remove\n */\n removeValidators(...validators: ValidatorFn<T>[]): void;\n\n /**\n * Checks if a validator exists inside this form\n *\n * @param validator the validator you want to check\n */\n hasValidator(validator: ValidatorFn<T>): boolean;\n}\n\n\nexport const createAbstractForm = <T, E> (getter: (() => T)): Form<T, E> => {\n const form = getter as Form<T, E>;\n\n const _validators = signal<ValidatorFn<T>[]>([]);\n Object.defineProperty(form, \"validators\", {\n value: _validators,\n writable: false\n });\n\n Object.defineProperty(form, \"addValidators\", {\n value: (...validators: ValidatorFn<T>[]): void => {\n const set = new Set<ValidatorFn<T>>(_validators());\n validators.forEach(validator => set.add(validator));\n\n _validators.set([...set]);\n }\n });\n\n Object.defineProperty(form, \"removeValidators\", {\n value: (...validators: ValidatorFn<T>[]): void => {\n const set = new Set<ValidatorFn<T>>(_validators());\n validators.forEach(validator => set.delete(validator));\n\n _validators.set([...set]);\n }\n });\n\n Object.defineProperty(form, \"hasValidator\", {\n value: (validator: ValidatorFn<T>): boolean => {\n const set = new Set<ValidatorFn<T>>(_validators());\n\n return set.has(validator);\n }\n });\n\n return form;\n}\n","import {createAbstractForm, Form} from \"./form\";\nimport {ValidationErrors, ValidatorFn} from \"./validator\";\nimport {computed, isSignal, signal, WritableSignal} from \"@angular/core\";\n\ninterface FormControlOptions<T> {\n value: T;\n disabled?: boolean;\n validators?: ValidatorFn<T>[];\n}\n\nexport interface FormControl<T> extends Form<T, ValidationErrors> {\n\n /**\n * Signal that declares if the control is disabled or not\n */\n readonly disabled: WritableSignal<boolean>;\n\n}\n\nexport const isFormControl = <T> (val: unknown): val is FormControl<T> => {\n return isSignal(val) &&\n 'errors' in val && isSignal(val.errors) &&\n 'validators' in val && isSignal(val.validators) &&\n 'disabled' in val && isSignal(val.disabled);\n};\n\nconst isSimpleFormOptions = <T> (val: T | FormControlOptions<T>): val is FormControlOptions<T> => {\n return !!val && typeof val === 'object' &&\n 'value' in val &&\n (!('disabled' in val) || typeof val.disabled === 'boolean') &&\n (!('validators' in val) || Array.isArray(val.validators));\n}\n\nexport function formControl<T>(val: T | FormControlOptions<T>): FormControl<T> {\n if (isSimpleFormOptions(val)) {\n return createSimpleForm(val);\n } else {\n return createSimpleForm({\n value: val,\n disabled: false,\n validators: [],\n })\n }\n}\n\nconst createSimpleForm = <T> (options: FormControlOptions<T>): FormControl<T> => {\n const form = createAbstractForm(signal(options.value)) as FormControl<T>;\n\n if (options.validators) {\n form.addValidators(...options.validators);\n }\n\n Object.defineProperty(form, 'errors', {\n value: computed(() => {\n const validators = form.validators();\n const value = form();\n\n const errors = [...validators].map(validator => validator(value))\n .filter((error): error is ValidationErrors => !!error);\n\n return errors.length > 0 ?\n errors.reduce((error, current) => Object.assign(error, current), {}) :\n null;\n })\n });\n\n Object.defineProperty(form, 'disabled', {\n value: signal(!!options.disabled)\n });\n\n return form;\n}\n","import {createAbstractForm, Form} from \"./form\";\nimport {ValidationErrors, ValidatorFn} from \"./validator\";\nimport {computed, signal} from \"@angular/core\";\nimport {isFormControl} from \"./form-control\";\nimport {isFunction} from \"lodash\";\n\nexport interface FormArray<F extends Form<any, any>> extends Form<FormArrayValue<F>, FormArrayErrors<FormError<F>>>, Iterable<F> {\n\n /**\n * Returns the amount of elements inside this FormArray\n */\n readonly length: number;\n\n /**\n * Access the control of the index. Returns undefined if the index is out of bounds\n */\n [index: number]: F | undefined;\n\n /**\n * Creates a new control based on a value and pushes it to this FormArray.\n *\n * @param value the value of the form array\n */\n push(value: FormValue<F>): void;\n\n /**\n * Removes the element at a certain index.\n * All elements after the index is going to be reordered.\n * If the index does not exist, this method will do nothing\n *\n * @param index the index you want to remove\n */\n removeAt(index: number): void;\n\n /**\n * Removes the last control of this FormArray and returns it.\n */\n pop(): void;\n\n /**\n * Disables all included Forms of this FormArray\n */\n disable(): void;\n\n /**\n * Enables all included Forms of this FormArray\n */\n enable(): void;\n\n /**\n * Calls a defined callback function on each element of an array, and returns an array that contains the results.\n *\n * @param callbackFn A function that accepts up to three arguments.\n * The map method calls the callbackFn function one time for each element in the array.\n */\n map<U>(callbackFn: (control: F, index: number, array: F[]) => U): U[];\n\n /**\n * Performs the specified action for each element in an array.\n * @param callbackFn A function that accepts up to three arguments.\n * forEach calls the callbackFn function one time for each element in the array.\n */\n forEach(callbackFn: (control: F, index: number, array: F[]) => void): void;\n\n}\n\ntype FormError<F> = F extends Form<any, infer E> ? E : never;\ntype FormValue<F> = F extends Form<infer T, any> ? T : never;\ntype FormArrayValue<F> = F extends Form<infer T, any> ? T[] : never;\n\nexport const formArrayFactory = <F extends Form<any, any>> (fn: (val: FormValue<F>) => F): (val: FormArrayValue<F>) => FormArray<F> => {\n return val => {\n const controls = signal(val.map(element => fn(element)));\n\n const form = createAbstractForm(computed(() => calcValue<F>(controls()))) as FormArray<F>;\n reindexArray(form, controls());\n\n Object.defineProperty(\n form,\n 'length',\n {\n get(): number {\n return controls().length;\n }\n }\n )\n\n Object.defineProperty(form, 'push', {\n value: (value: FormValue<F>) => {\n const control = fn(value);\n controls.update(c => [...c, control]);\n\n reindexArray(form, controls());\n }\n });\n\n Object.defineProperty(form, 'removeAt', {\n value: (index: number) => {\n controls.update(controls => controls.filter((_control, i) => i !== index));\n\n reindexArray(form, controls());\n }\n })\n\n Object.defineProperty(form, 'pop', {\n value: () => {\n controls.update(c => {\n c.pop();\n\n return [...c];\n });\n\n Object.defineProperty(form, controls().length, {\n value: undefined,\n configurable: true\n });\n }\n });\n\n Object.defineProperty(form, Symbol.iterator, {\n get(): any {\n return controls()[Symbol.iterator];\n }\n });\n\n Object.defineProperty(form, 'errors', {\n value: computed(() => {\n // construct Error object\n const groupErrors = calcGroupErrors<F>(form(), form.validators());\n const controlErrors = calcControlErrors<F>(controls());\n\n if (groupErrors === null && Object.keys(controlErrors).length === 0) {\n return null;\n }\n\n return {\n this: groupErrors,\n controls: controlErrors\n } satisfies FormArrayErrors<F>;\n })\n });\n\n Object.defineProperty(form, 'set', {\n value: (array: FormArrayValue<F>) => {\n const properties = Object.fromEntries(\n controls().map((_control, index) => {\n return [index, {\n value: undefined,\n configurable: true\n }];\n })\n );\n\n controls.set(array.map(element => fn(element)));\n Object.assign(properties, Object.fromEntries(\n controls().map((control, index) => {\n return [index, {\n value: control,\n configurable: true\n }]\n })\n ));\n\n Object.defineProperties(form, properties);\n }\n });\n\n Object.defineProperty(form, 'update', {\n value: (fn: (val: FormArrayValue<F>) => FormArrayValue<F>) => {\n form.set(fn(form()));\n }\n });\n\n Object.defineProperty(form, 'disable', {\n value: () => {\n controls().forEach(control => {\n if (isFormControl(control)) {\n control.disabled.set(true);\n } else if ('disable' in control && isFunction(control.disable)) {\n control.disable();\n }\n });\n }\n });\n\n Object.defineProperty(form, 'enable', {\n value: () => {\n controls().forEach(control => {\n if (isFormControl(control)) {\n control.disabled.set(false);\n } else if ('enable' in control && isFunction(control.enable)) {\n control.enable();\n }\n })\n }\n });\n\n Object.defineProperty(form, 'map', {\n value: <U> (callbackFn: (control: F, index: number, array: F[]) => U) => {\n return controls().map(callbackFn);\n }\n });\n\n Object.defineProperty(form, 'forEach', {\n value: (callbackFn: (control: F, index: number, array: F[]) => void) => {\n controls().forEach(callbackFn);\n }\n });\n\n return form;\n }\n}\n\nconst calcValue = <F extends Form<any, any>>(controls: F[]): FormArrayValue<F> => {\n return controls.map(control => control()) as FormArrayValue<F>;\n}\n\nconst reindexArray = <F extends Form<any, any>>(form: FormArray<F>, controls: F[]): void => {\n Object.defineProperties(\n form,\n Object.fromEntries(\n controls.map((control, index) => {\n return [index, {\n value: control,\n configurable: true\n }];\n })\n )\n );\n}\n\nconst calcGroupErrors = <F>(value: FormArrayValue<F>, validators: ValidatorFn<FormArrayValue<F>>[]): ValidationErrors | null => {\n const errors = validators.map(validator => validator(value))\n .filter((error): error is ValidationErrors => !!error);\n\n if (errors.length === 0) {\n return null;\n }\n\n return errors.reduce((error, current) => Object.assign(error, current), {});\n}\n\nconst calcControlErrors = <F extends Form<any, any>>(controls: F[]): FormError<F>[] => {\n return controls.map(control => control.errors());\n}\n\n\nexport interface FormArrayErrors<F> {\n this: ValidationErrors | null;\n controls: FormError<F>[]\n}\n","import {createAbstractForm, Form} from \"./form\";\nimport {computed, signal} from \"@angular/core\";\nimport {ValidationErrors, ValidatorFn} from \"./validator\";\nimport {isFunction} from \"lodash\";\nimport {isFormControl} from \"./form-control\";\n\nconst forbiddenNames = [\n 'addValidators',\n 'removeValidators',\n 'setControl',\n 'hasValidator',\n 'errors',\n 'validators',\n]\n\nexport type FormGroup<T extends {[K in keyof T]: T[K]}, F extends TForm<T>> = GroupControls<T, F> & Form<T, FormGroupErrors<T, F>> & {\n\n /**\n * Set Control of optional keys in T.\n * Controls that are not set, will have undefined keys in their value.\n * If your set the control with null, you will remove the control\n *\n * @param key the key you want to set\n * @param control the control you want to add\n */\n setControl<K extends OptionalKeys<T>>(key: K, control: F[K] | null): void;\n\n /**\n * Disables all included Forms of this FormGroup\n */\n disable(): void;\n\n /**\n * Enables all included Forms of this FormGroup\n */\n enable(): void;\n};\n\nexport const formGroupFactory = <T extends {[K in keyof T]: T[K]}, F extends TForm<T>> (fn: (val?: T) => Required<F>): (val?: T) => FormGroup<T, F> => {\n return (val?: T) => {\n const controls = signal(fn(val));\n const form = createAbstractForm<T, F>(computed(() => calcValue<T, F>(controls()))) as FormGroup<T, F>;\n\n // Register Controls\n Object.entries<Form<any, any> | undefined>(controls())\n .filter((arr): arr is [keyof T & string, Form<any, any>] => !!arr[1])\n .forEach(([k, v]) => {\n if (!forbiddenNames.includes(k)) {\n Object.defineProperty(form, k, {\n value: v,\n configurable: true\n });\n } else {\n throw new Error(`It is not allowed to name a property \"${k}\" inside a FormGroup. Please rename it`)\n }\n });\n\n Object.defineProperty(form, 'errors', {\n value: computed(() => {\n // construct Error object\n const groupErrors = calcGroupErrors<T>(form(), form.validators());\n const controlErrors = calcControlErrors<T, F>(controls());\n\n if (groupErrors === null && Object.keys(controlErrors).length === 0) {\n return null;\n }\n\n return {\n this: groupErrors,\n controls: controlErrors\n };\n })\n });\n\n Object.defineProperty(form, 'set', {\n value: (val: T) => {\n Object.entries<Form<any, any> | undefined>(controls())\n .filter((arr): arr is [keyof T & string, Form<any, any>] => !!arr[1])\n .forEach(([key, control]) => {\n control.set(val[key]);\n });\n }\n });\n\n Object.defineProperty(form, 'update', {\n value: (fn: (val: T) => T): void => {\n form.set(fn(form()))\n }\n });\n\n Object.defineProperty(form, 'setControl', {\n value: <K extends OptionalKeys<T>>(key: K, control: F[K] | null) => {\n if (control) {\n controls.update(c => {\n const newControls = {...c};\n newControls[key] = control;\n\n return newControls;\n });\n\n Object.defineProperty(form, key, {value: control, configurable: true});\n } else {\n controls.update(c => {\n const newControls = {...c};\n delete newControls[key];\n\n return newControls;\n });\n\n Object.defineProperty(form, key, {value: undefined, configurable: true});\n }\n }\n });\n\n Object.defineProperty(form, 'disable', {\n value: () => {\n Object.values<Form<any, any> | undefined>(controls())\n .filter((control): control is Form<any, any> => !!control)\n .forEach(control => {\n if (isFormControl(control)) {\n control.disabled.set(true);\n } else if ('disable' in control && isFunction(control.disable)) {\n control.disable();\n }\n });\n }\n });\n\n Object.defineProperty(form, 'enable', {\n value: () => {\n Object.values<Form<any, any> | undefined>(controls())\n .filter((control): control is Form<any, any> => !!control)\n .forEach(control => {\n if (isFormControl(control)) {\n control.disabled.set(false);\n } else if ('disable' in control && isFunction(control.disable)) {\n control.disable();\n }\n })\n }\n })\n\n return form;\n }\n}\n\nconst calcValue = <T extends {[K in keyof T]: T[K]}, F extends TForm<T>>(controls: F): T => {\n return Object.fromEntries(\n Object.entries<Form<any, any> | undefined>(controls)\n .filter((arr): arr is [string, Form<any, any>] => !!arr[1])\n .map(([key, val]) => [key, val()])\n ) as T;\n}\n\nconst calcGroupErrors = <T extends {[K in keyof T]: T[K]}>(value: T, validators: ValidatorFn<T>[]): ValidationErrors | null => {\n const errors = validators.map(validator => validator(value))\n .filter((error): error is ValidationErrors => !!error);\n\n if (errors.length === 0) {\n return null;\n }\n\n return errors.reduce((error, current) => Object.assign(error, current), {});\n}\n\nconst calcControlErrors = <T extends {[K in keyof T]: T[K]}, F extends TForm<T>>(controls: F): ControlErrors<T, F> => {\n return Object.fromEntries(\n Object.entries<Form<any, any> | undefined>(controls)\n .filter((arr): arr is [string, Form<any, any>] => !!arr[1])\n .map(([key, val]) => [key, val.errors()])\n .filter(([_key, val]) => !!val)\n ) satisfies ControlErrors<T, F>;\n}\n\nexport interface FormGroupErrors<T extends {[K in keyof T]: T[K]}, F extends TForm<T>> {\n this: ValidationErrors | null;\n controls: ControlErrors<T, F>\n}\n\nexport type ControlErrors<T extends {[K in keyof T]: T[K]}, F extends TForm<T>> = Partial<{\n [K in keyof F]: F[K] extends Form<any, infer E> ? E : never;\n}>;\n\nexport type TForm<T> = {\n [K in keyof T]: K extends OptionalKeys<T> ?\n (Form<Exclude<T[K], undefined>, any> | undefined) :\n Form<Exclude<T[K], undefined>, any>;\n}\n\n// Select only Optional Keys\nexport type OptionalKeys<T> = {\n [K in keyof T]-?: undefined extends T[K] ? K : never;\n}[keyof T];\n\nexport type GroupControls<T extends {[K in keyof T]: T[K]}, F extends TForm<T>> = {\n [K in keyof T]: K extends OptionalKeys<T> ? F[K] | undefined : F[K]\n}\n\n","import {FormControl} from \"../models/form-control\";\n\nexport class AbstractFormDirective<T> {\n\n protected updateValue(form: FormControl<T>, value: T): void {\n form.set(value);\n }\n}\n","import { Directive, effect, ElementRef, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'textarea[form], input:not([matDatepicker]):not([matSliderThumb]):not([matSliderStartThumb]):not([matSliderEndThumb]):not([type=number]):not([type=checkbox]):not([type=date]):not([type=datetime-local]):not([type=time]):not([type=radio]):not([type=range]):not([type=file])[form]',\n standalone: false\n})\nexport class InputTextDirective extends AbstractFormDirective<string> {\n\n public readonly form = input<FormControl<string>>();\n\n private readonly element = inject<ElementRef<HTMLInputElement | HTMLTextAreaElement>>(ElementRef);\n\n public constructor() {\n super();\n\n // Update input when form value is updated\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.nativeElement.value = form();\n this.element.nativeElement.disabled = form.disabled();\n }\n });\n }\n\n @HostListener('input')\n public onInput(): void {\n const form = this.form();\n\n if (form) {\n this.updateValue(form, this.element.nativeElement.value);\n }\n }\n}\n","import { Directive, effect, ElementRef, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'input[type=number][form]',\n standalone: false\n})\nexport class InputNumberDirective extends AbstractFormDirective<number> {\n\n public readonly form = input<FormControl<number>>();\n\n private readonly element = inject<ElementRef<HTMLInputElement>>(ElementRef);\n\n public constructor() {\n super();\n\n // Update input when form value is updated\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.nativeElement.valueAsNumber = form();\n this.element.nativeElement.disabled = form.disabled();\n }\n });\n }\n\n /**\n * Blocks incoming input that are not numbers\n *\n * @param event\n */\n @HostListener('keydown', ['$event'])\n public onKeyDown(event: KeyboardEvent): void {\n if (event.key.length === 1 && !/[-+.0-9]/.test(event.key)) {\n event.preventDefault();\n }\n }\n\n @HostListener('input')\n public onInput(): void {\n const form = this.form();\n\n if (form) {\n this.updateValue(form, this.element.nativeElement.valueAsNumber);\n }\n }\n\n}\n","import { Directive, effect, HostListener, input, inject } from '@angular/core';\nimport {MatCheckbox} from \"@angular/material/checkbox\";\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'mat-checkbox[form]',\n standalone: false\n})\nexport class MatCheckboxDirective extends AbstractFormDirective<boolean> {\n\n public readonly form = input<FormControl<boolean>>();\n\n private readonly element = inject(MatCheckbox, { self: true });\n\n public constructor() {\n super();\n\n // Update input when form value is updated\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.writeValue(form());\n this.element.setDisabledState(form.disabled());\n }\n });\n }\n\n @HostListener('input')\n public onValueChange() {\n const form = this.form();\n\n if (form) {\n this.updateValue(form, this.element.checked);\n }\n }\n}\n","import { Directive, effect, ElementRef, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'input[type=checkbox][form]',\n standalone: false\n})\nexport class InputCheckboxDirective extends AbstractFormDirective<boolean> {\n\n public readonly form = input<FormControl<boolean>>();\n\n private readonly element = inject<ElementRef<HTMLInputElement>>(ElementRef);\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.nativeElement.checked = form();\n this.element.nativeElement.disabled = form.disabled();\n }\n });\n }\n\n @HostListener('input')\n public onChange() {\n const form = this.form();\n\n if (form) {\n this.updateValue(form, this.element.nativeElement.checked);\n }\n }\n}\n","import { Directive, effect, ElementRef, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'input[type=date][form], input[type=datetime-local][form], input[type=time][form]',\n standalone: false\n})\nexport class InputDateDirective extends AbstractFormDirective<Date | null> {\n\n public readonly form = input<FormControl<Date | null>>();\n\n private readonly element = inject<ElementRef<HTMLInputElement>>(ElementRef);\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.nativeElement.valueAsDate = form();\n this.element.nativeElement.disabled = form.disabled();\n }\n });\n }\n\n @HostListener('input')\n protected onInput(): void {\n const form = this.form();\n\n if (form) {\n super.updateValue(form, this.element.nativeElement.valueAsDate);\n }\n }\n\n}\n","import { Directive, effect, ElementRef, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'input[type=radio][form]',\n standalone: false\n})\nexport class InputRadioDirective extends AbstractFormDirective<string> {\n\n public readonly form = input<FormControl<string>>();\n\n private readonly element = inject<ElementRef<HTMLInputElement>>(ElementRef);\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.nativeElement.checked = form() === this.element.nativeElement.value;\n this.element.nativeElement.disabled = form.disabled();\n }\n });\n }\n\n @HostListener('change')\n public onClick(): void {\n const form = this.form();\n if (form) {\n const checked = this.element.nativeElement.checked;\n\n if (checked) {\n this.updateValue(form, this.element.nativeElement.value);\n }\n }\n }\n\n}\n","import { Directive, effect, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {MatRadioChange, MatRadioGroup} from \"@angular/material/radio\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'mat-radio-group[form]',\n standalone: false\n})\nexport class MatRadioGroupDirective<T> extends AbstractFormDirective<T> {\n\n public readonly form = input<FormControl<T>>();\n\n private readonly element = inject(MatRadioGroup);\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.writeValue(form());\n this.element.setDisabledState(form.disabled());\n }\n });\n }\n\n @HostListener('change', ['$event'])\n public onValueChange(event: MatRadioChange): void {\n const form = this.form();\n\n if (form) {\n this.updateValue(form, event.value);\n }\n }\n}\n","import { Directive, effect, ElementRef, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'input[type=range][form]',\n standalone: false\n})\nexport class InputRangeDirective extends AbstractFormDirective<number> {\n\n public readonly form = input<FormControl<number>>();\n\n private readonly element = inject<ElementRef<HTMLInputElement>>(ElementRef);\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.nativeElement.valueAsNumber = form();\n this.element.nativeElement.disabled = form.disabled();\n }\n });\n }\n\n @HostListener('input')\n public onInput() {\n const form = this.form();\n if (form) {\n this.updateValue(form, this.element.nativeElement.valueAsNumber);\n }\n }\n\n}\n","import { booleanAttribute, Directive, effect, ElementRef, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'input[multiple][type=file][form], input[type=\"file\"][form]',\n standalone: false\n})\nexport class InputFileDirective extends AbstractFormDirective<File | File[] | null> {\n\n public readonly multiple = input(false, {transform: booleanAttribute});\n public readonly form = input<FormControl<File | null> | FormControl<File[] | null>>();\n\n private readonly element = inject<ElementRef<HTMLInputElement>>(ElementRef);\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n const val = form();\n if (val) {\n const list = new DataTransfer();\n\n if (Array.isArray(val)) {\n val.forEach(item => list.items.add(item));\n } else {\n list.items.add(val);\n }\n\n this.element.nativeElement.files = list.files;\n } else {\n this.element.nativeElement.files = null;\n }\n\n this.element.nativeElement.disabled = form.disabled();\n }\n });\n }\n\n @HostListener('input')\n public onFileChange(): void {\n const form = this.form();\n\n if (form) {\n if (this.element.nativeElement.files) {\n const file = this.element.nativeElement.files;\n\n if (this.isFileArrayForm(form)) {\n const arr = [];\n for (let i = 0; i < file.length; i++) {\n const item = file.item(i);\n if (item) {\n arr.push(item);\n }\n }\n\n form.set(arr);\n } else {\n form.set(file.item(0));\n }\n } else {\n form.set(null);\n }\n }\n }\n\n private isFileArrayForm(_val: FormControl<File | null> | FormControl<File[] | null>): _val is FormControl<File[] | null> {\n return this.multiple();\n }\n}\n","import { Directive, effect, ElementRef, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'select[form]',\n standalone: false\n})\nexport class SelectDirective extends AbstractFormDirective<string> {\n\n public readonly form = input<FormControl<string>>();\n\n private readonly element = inject<ElementRef<HTMLSelectElement>>(ElementRef);\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.nativeElement.value = form();\n this.element.nativeElement.disabled = form.disabled();\n }\n });\n }\n\n @HostListener('change')\n public onChange(): void {\n const form = this.form();\n\n if (form) {\n this.updateValue(form, this.element.nativeElement.value);\n }\n }\n}\n","import {Directive, effect, forwardRef, HostListener, input} from \"@angular/core\";\nimport {FormControl} from \"../../models/form-control\";\nimport {MatSliderThumb} from \"@angular/material/slider\";\n\n@Directive({\n selector: 'input[matSliderThumb][form]',\n providers: [\n {\n provide: MatSliderThumb,\n useExisting: forwardRef(() => MatSliderThumbDirective),\n }\n ],\n standalone: false\n})\nexport class MatSliderThumbDirective extends MatSliderThumb {\n\n public readonly form = input<FormControl<number>>();\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.writeValue(form());\n this.setDisabledState(form.disabled());\n }\n });\n }\n\n @HostListener('input')\n public onInput() {\n const form = this.form();\n\n if (form) {\n form.set(this.value);\n }\n }\n\n}\n","import { Directive, effect, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {MatSlideToggle} from \"@angular/material/slide-toggle\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'mat-slide-toggle[form]',\n standalone: false\n})\nexport class MatSlideToggleDirective extends AbstractFormDirective<boolean> {\n\n public readonly form = input<FormControl<boolean>>();\n\n private readonly element = inject(MatSlideToggle);\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.writeValue(form());\n this.element.setDisabledState(form.disabled());\n }\n });\n }\n\n @HostListener('change')\n public onChange() {\n const form = this.form();\n\n if (form) {\n this.updateValue(form, this.element.checked);\n }\n }\n}\n","import { Directive, effect, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {MatSelect, MatSelectChange} from \"@angular/material/select\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'mat-select[form]',\n standalone: false\n})\nexport class MatSelectDirective<T> extends AbstractFormDirective<T | null | T[]> {\n\n public readonly form = input<FormControl<T> | FormControl<T | null> | FormControl<T[]>>();\n\n private readonly element = inject(MatSelect);\n\n constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.writeValue(form());\n this.element.setDisabledState(form.disabled());\n }\n });\n }\n\n @HostListener('selectionChange', ['$event'])\n public onChange(event: MatSelectChange<T | T[]>) {\n const form = this.form();\n\n if (form) {\n if (this.isArrayForm(form)) {\n const arr = Array.isArray(event.value) ? event.value : [event.value];\n\n form.set(arr);\n } else {\n form.set(event.value as T);\n }\n }\n }\n\n private isArrayForm(_form: FormControl<T> | FormControl<T | null> | FormControl<T[]>): _form is FormControl<T[]> {\n return this.element.multiple;\n }\n}\n","import { Directive, effect, HostListener, input, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {MatButtonToggleChange, MatButtonToggleGroup} from \"@angular/material/button-toggle\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'mat-button-toggle-group[form]',\n standalone: false\n})\nexport class MatButtonToggleGroupDirective<T> extends AbstractFormDirective<T | T[]> {\n\n public readonly form = input<FormControl<T> | FormControl<T[]>>();\n\n public readonly element = inject(MatButtonToggleGroup);\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.element.writeValue(form());\n this.element.setDisabledState(form.disabled());\n }\n });\n }\n\n @HostListener('change', ['$event'])\n public onChange(event: MatButtonToggleChange): void {\n const form = this.form();\n\n if (form) {\n form.set(event.value);\n }\n }\n\n}\n","import {Directive, effect, forwardRef, HostListener, input} from '@angular/core';\nimport {MatSliderRangeThumb} from \"@angular/material/slider\";\nimport {FormControl} from \"../../models/form-control\";\n\n@Directive({\n selector: 'input[matSliderStartThumb][form], input[matSliderEndThumb][form]',\n providers: [\n {\n provide: MatSliderRangeThumb,\n useExisting: forwardRef(() => MatSliderRangeThumbDirective),\n }\n ],\n standalone: false\n})\nexport class MatSliderRangeThumbDirective extends MatSliderRangeThumb {\n\n public readonly form = input<FormControl<number>>();\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.writeValue(form());\n this.setDisabledState(form.disabled());\n }\n });\n }\n\n @HostListener('input')\n public onInput() {\n const form = this.form();\n\n if (form) {\n form.set(this.value);\n }\n }\n}\n","import { Directive, effect, input, OnDestroy, inject } from '@angular/core';\nimport {AbstractFormDirective} from \"../abstract-form-directive\";\nimport {MatDatepickerInput} from \"@angular/material/datepicker\";\nimport {FormControl} from \"../../models/form-control\";\nimport {Subscription} from \"rxjs\";\n\n@Directive({\n selector: 'input[matDatepicker][form]',\n standalone: false\n})\nexport class MatDatepickerDirective<D> extends AbstractFormDirective<D | null> implements OnDestroy {\n\n public readonly form = input<FormControl<D | null>>();\n\n private readonly datePicker = inject<MatDatepickerInput<D | null>>(MatDatepickerInput);\n private readonly subscription = new Subscription();\n\n public constructor() {\n super();\n\n effect(() => {\n const form = this.form();\n\n if (form) {\n this.datePicker.writeValue(form());\n this.datePicker.setDisabledState(form.disabled());\n }\n });\n\n this.subscription.add(\n this.datePicker.dateChange.subscribe(date => {\n const form = this.form();\n\n if (form) {\n this.updateValue(form, date.value);\n }\n })\n );\n }\n\n public ngOnDestroy() {\n this.subscription.unsubscribe();\n }\n}\n","import {NgModule} from '@angular/core';\nimport {CommonModule} from '@angular/common';\nimport {InputTextDirective} from \"./directives/input/input-text.directive\";\nimport {InputNumberDirective} from \"./directives/input/input-number.directive\";\nimport {MatCheckboxDirective} from \"./directives/material/mat-checkbox.directive\";\nimport {InputCheckboxDirective} from \"./directives/input/input-checkbox.directive\";\nimport {InputDateDirective} from \"./directives/input/input-date.directive\";\nimport {InputRadioDirective} from \"./directives/input/input-radio.directive\";\nimport {MatRadioGroupDirective} from \"./directives/material/mat-radio-group.directive\";\nimport {InputRangeDirective} from \"./directives/input/input-range.directive\";\nimport {InputFileDirective} from \"./directives/input/input-file.directive\";\nimport {SelectDirective} from \"./directives/select/select.directive\";\nimport {MatSliderThumbDirective} from \"./directives/material/mat-slider-thumb.directive\";\nimport {MatSlideToggleDirective} from \"./directives/material/mat-slide-toggle.directive\";\nimport {MatSelectDirective} from \"./directives/material/mat-select.directive\";\nimport {MatButtonToggleGroupDirective} from \"./directives/material/mat-button-toggle-group.directive\";\nimport {MatSliderRangeThumbDirective} from \"./directives/material/mat-slider-range-thumb.directive\";\nimport {MatDatepickerDirective} from \"./directives/material/mat-datepicker.directive\";\n\nexport * from './directives/abstract-form-directive';\n\nexport * from './directives/input/input-checkbox.directive';\nexport * from './directives/input/input-date.directive';\nexport * from './directives/input/input-file.directive';\nexport * from './directives/input/input-number.directive';\nexport * from './directives/input/input-radio.directive';\nexport * from './directives/input/input-range.directive';\nexport * from './directives/input/input-text.directive';\n\nexport * from './directives/select/select.directive';\n\nexport * from './directives/material/mat-button-toggle-group.directive';\nexport * from './directives/material/mat-checkbox.directive';\nexport * from './directives/material/mat-datepicker.directive';\nexport * from './directives/material/mat-radio-group.directive';\nexport * from './directives/material/mat-select.directive';\nexport * from './directives/material/mat-slide-toggle.directive';\nexport * from './directives/material/mat-slider-range-thumb.directive';\nexport * from './directives/material/mat-slider-thumb.directive';\n\n@NgModule({\n declarations: [\n InputCheckboxDirective,\n InputDateDirective,\n InputFileDirective,\n InputNumberDirective,\n InputRadioDirective,\n InputRangeDirective,\n InputTextDirective,\n\n SelectDirective,\n\n MatButtonToggleGroupDirective,\n MatCheckboxDirective,\n MatDatepickerDirective,\n MatRadioGroupDirective,\n MatSelectDirective,\n MatSlideToggleDirective,\n MatSliderRangeThumbDirective,\n MatSliderThumbDirective\n ],\n imports: [\n CommonModule,\n ],\n exports: [\n InputCheckboxDirective,\n InputDateDirective,\n InputFileDirective,\n InputNumberDirective,\n InputRadioDirective,\n InputRangeDirective,\n InputTextDirective,\n\n SelectDirective,\n\n MatButtonToggleGroupDirective,\n MatCheckboxDirective,\n MatDatepickerDirective,\n MatRadioGroupDirective,\n MatSelectDirective,\n MatSlideToggleDirective,\n MatSliderRangeThumbDirective,\n MatSliderThumbDirective\n ]\n})\nexport class FormsModule {\n}\n","/*\n * Public API Surface of form-signals\n */\n\nexport type {ValidationErrors, ValidatorFn} from './lib/models/validator';\nexport type {Form, FormFactoryType} from './lib/models/form';\n\nexport * from './lib/models/form-array';\nexport * from './lib/models/form-group';\nexport * from './lib/models/form-control';\n\nexport * from './lib/forms.module';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["calcValue","calcGroupErrors","calcControlErrors"],"mappings":"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