{"version":3,"file":"signals.mjs","sources":["../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/field/di.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/di.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/disabled.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/hidden.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/readonly.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/util.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/validate.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/validation_errors.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/email.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/max.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/max_length.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/min.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/min_length.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/pattern.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/required.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/validate_async.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/validate_tree.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/standard_schema.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/validation/validate_http.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/rules/debounce.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/directive/parse_errors.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/util/parser.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/api/transformed_value.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/controls/interop_ng_control.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/directive/bindings.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/directive/native.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/directive/control_custom.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/directive/control_cva.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/directive/select.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/directive/control_native.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/directive/form_field_directive.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/forms/signals/src/directive/ng_signal_form.ts"],"sourcesContent":["/**\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\nimport {InjectionToken} from '@angular/core';\nimport type {SignalFormsConfig} from '../api/di';\n\n/** Injection token for the signal forms configuration. */\nexport const SIGNAL_FORMS_CONFIG = new InjectionToken(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'SIGNAL_FORMS_CONFIG' : '',\n);\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\nimport {type Provider} from '@angular/core';\nimport {SIGNAL_FORMS_CONFIG} from '../field/di';\nimport type {FormField} from '../directive/form_field_directive';\n\n/**\n * Configuration options for signal forms.\n *\n * @experimental 21.0.1\n */\nexport interface SignalFormsConfig {\n /** A map of CSS class names to predicate functions that determine when to apply them. */\n classes?: {[className: string]: (state: FormField) => boolean};\n}\n\n/**\n * Provides configuration options for signal forms.\n *\n * @experimental 21.0.1\n */\nexport function provideSignalFormsConfig(config: SignalFormsConfig): Provider[] {\n return [{provide: SIGNAL_FORMS_CONFIG, useValue: config}];\n}\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\nimport {FieldPathNode} from '../../schema/path_node';\nimport {assertPathIsCurrent} from '../../schema/schema';\nimport type {FieldContext, LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../types';\n\n/**\n * Adds logic to a field to conditionally disable it. A disabled field does not contribute to the\n * validation, touched/dirty, or other state of its parent field.\n *\n * @param path The target path to add the disabled logic to.\n * @param logic A reactive function that returns `true` (or a string reason) when the field is disabled,\n * and `false` when it is not disabled.\n * @template TValue The type of value stored in the field the logic is bound to.\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @category logic\n * @experimental 21.0.0\n */\nexport function disabled(\n path: SchemaPath,\n logic?: string | NoInfer>,\n): void {\n assertPathIsCurrent(path);\n\n const pathNode = FieldPathNode.unwrapFieldPath(path);\n pathNode.builder.addDisabledReasonRule((ctx) => {\n let result: boolean | string = true;\n if (typeof logic === 'string') {\n result = logic;\n } else if (logic) {\n result = logic(ctx as FieldContext);\n }\n if (typeof result === 'string') {\n return {fieldTree: ctx.fieldTree, message: result};\n }\n return result ? {fieldTree: ctx.fieldTree} : undefined;\n });\n}\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\nimport {FieldPathNode} from '../../schema/path_node';\nimport {assertPathIsCurrent} from '../../schema/schema';\nimport type {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../types';\n\n/**\n * Adds logic to a field to conditionally hide it. A hidden field does not contribute to the\n * validation, touched/dirty, or other state of its parent field.\n *\n * If a field may be hidden it is recommended to guard it with an `@if` in the template:\n * ```\n * @if (!email().hidden()) {\n * \n * \n * }\n * ```\n *\n * @param path The target path to add the hidden logic to.\n * @param logic A reactive function that returns `true` when the field is hidden.\n * @template TValue The type of value stored in the field the logic is bound to.\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @category logic\n * @experimental 21.0.0\n */\nexport function hidden(\n path: SchemaPath,\n logic: NoInfer>,\n): void {\n assertPathIsCurrent(path);\n\n const pathNode = FieldPathNode.unwrapFieldPath(path);\n pathNode.builder.addHiddenRule(logic);\n}\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\nimport {FieldPathNode} from '../../schema/path_node';\nimport {assertPathIsCurrent} from '../../schema/schema';\nimport type {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../types';\n\n/**\n * Adds logic to a field to conditionally make it readonly. A readonly field does not contribute to\n * the validation, touched/dirty, or other state of its parent field.\n *\n * @param path The target path to make readonly.\n * @param logic A reactive function that returns `true` when the field is readonly.\n * @template TValue The type of value stored in the field the logic is bound to.\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @category logic\n * @experimental 21.0.0\n */\nexport function readonly(\n path: SchemaPath,\n logic: NoInfer> = () => true,\n) {\n assertPathIsCurrent(path);\n\n const pathNode = FieldPathNode.unwrapFieldPath(path);\n pathNode.builder.addReadonlyRule(logic);\n}\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\nimport {LogicFn, OneOrMany, PathKind, type FieldContext} from '../../types';\nimport {ValidationError} from './validation_errors';\n\n/** Represents a value that has a length or size, such as an array or string, or set. */\nexport type ValueWithLengthOrSize = {length: number} | {size: number};\n\n/** Common options available on the standard validators. */\nexport type BaseValidatorConfig =\n | {\n /** A user-facing error message to include with the error. */\n message?: string | LogicFn;\n error?: never;\n }\n | {\n /**\n * Custom validation error(s) to report instead of the default,\n * or a function that receives the `FieldContext` and returns custom validation error(s).\n */\n error?: OneOrMany | LogicFn, TPathKind>;\n message?: never;\n };\n\n/** Gets the length or size of the given value. */\nexport function getLengthOrSize(value: ValueWithLengthOrSize) {\n const v = value as {length: number; size: number};\n return typeof v.length === 'number' ? v.length : v.size;\n}\n\n/**\n * Gets the value for an option that may be either a static value or a logic function that produces\n * the option value.\n *\n * @param opt The option from BaseValidatorConfig.\n * @param ctx The current FieldContext.\n * @returns The value for the option.\n */\nexport function getOption(\n opt: Exclude | LogicFn | undefined,\n ctx: FieldContext,\n): TOption | undefined {\n return opt instanceof Function ? opt(ctx) : opt;\n}\n\n/**\n * Checks if the given value is considered empty. Empty values are: null, undefined, '', false, NaN.\n */\nexport function isEmpty(value: unknown): boolean {\n if (typeof value === 'number') {\n return isNaN(value);\n }\n return value === '' || value === false || value == null;\n}\n\n/**\n * Normalizes validation errors (which can be a single error, an array of errors, or undefined)\n * into a list of errors.\n */\nexport function normalizeErrors(error: OneOrMany | undefined): readonly T[] {\n if (error === undefined) {\n return [];\n }\n if (Array.isArray(error)) {\n return error as readonly T[];\n }\n return [error as T];\n}\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\nimport {addDefaultField} from '../../../field/validation';\nimport {FieldPathNode} from '../../../schema/path_node';\nimport {assertPathIsCurrent} from '../../../schema/schema';\nimport type {\n FieldContext,\n FieldValidator,\n PathKind,\n SchemaPath,\n SchemaPathRules,\n} from '../../types';\n\n/**\n * Adds logic to a field to determine if the field has validation errors.\n *\n * @param path The target path to add the validation logic to.\n * @param logic A `Validator` that returns the current validation errors.\n * @template TValue The type of value stored in the field the logic is bound to.\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @category logic\n * @experimental 21.0.0\n */\nexport function validate(\n path: SchemaPath,\n logic: NoInfer>,\n): void {\n assertPathIsCurrent(path);\n\n const pathNode = FieldPathNode.unwrapFieldPath(path);\n pathNode.builder.addSyncErrorRule((ctx) => {\n return addDefaultField(logic(ctx as FieldContext), ctx.fieldTree);\n });\n}\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\nimport type {FormField} from '../../../directive/form_field_directive';\nimport type {FieldTree} from '../../types';\nimport type {StandardSchemaValidationError} from './standard_schema';\n\n/**\n * Options used to create a `ValidationError`.\n */\nexport interface ValidationErrorOptions {\n /** Human readable error message. */\n message?: string;\n}\n\n/**\n * A type that requires the given type `T` to have a `field` property.\n * @template T The type to add a `field` to.\n *\n * @experimental 21.0.0\n */\nexport type WithFieldTree = T & {fieldTree: FieldTree};\n/** @deprecated Use `WithFieldTree` instead */\nexport type WithField = WithFieldTree;\n\n/**\n * A type that allows the given type `T` to optionally have a `field` property.\n * @template T The type to optionally add a `field` to.\n *\n * @experimental 21.0.0\n */\nexport type WithOptionalFieldTree = Omit & {fieldTree?: FieldTree};\n/** @deprecated Use `WithOptionalFieldTree` instead */\nexport type WithOptionalField = WithOptionalFieldTree;\n\n/**\n * A type that ensures the given type `T` does not have a `field` property.\n * @template T The type to remove the `field` from.\n *\n * @experimental 21.0.0\n */\nexport type WithoutFieldTree = T & {fieldTree: never};\n/** @deprecated Use `WithoutFieldTree` instead */\nexport type WithoutField = WithoutFieldTree;\n\n/**\n * Create a required error associated with the target field\n * @param options The validation error options\n *\n * @experimental 21.0.0\n */\nexport function requiredError(\n options: WithFieldTree,\n): RequiredValidationError;\n/**\n * Create a required error\n * @param options The optional validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function requiredError(\n options?: ValidationErrorOptions,\n): WithoutFieldTree;\nexport function requiredError(\n options?: ValidationErrorOptions,\n): WithOptionalFieldTree {\n return new RequiredValidationError(options);\n}\n\n/**\n * Create a min value error associated with the target field\n * @param min The min value constraint\n * @param options The validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function minError(\n min: number,\n options: WithFieldTree,\n): MinValidationError;\n/**\n * Create a min value error\n * @param min The min value constraint\n * @param options The optional validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function minError(\n min: number,\n options?: ValidationErrorOptions,\n): WithoutFieldTree;\nexport function minError(\n min: number,\n options?: ValidationErrorOptions,\n): WithOptionalFieldTree {\n return new MinValidationError(min, options);\n}\n\n/**\n * Create a max value error associated with the target field\n * @param max The max value constraint\n * @param options The validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function maxError(\n max: number,\n options: WithFieldTree,\n): MaxValidationError;\n/**\n * Create a max value error\n * @param max The max value constraint\n * @param options The optional validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function maxError(\n max: number,\n options?: ValidationErrorOptions,\n): WithoutFieldTree;\nexport function maxError(\n max: number,\n options?: ValidationErrorOptions,\n): WithOptionalFieldTree {\n return new MaxValidationError(max, options);\n}\n\n/**\n * Create a minLength error associated with the target field\n * @param minLength The minLength constraint\n * @param options The validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function minLengthError(\n minLength: number,\n options: WithFieldTree,\n): MinLengthValidationError;\n/**\n * Create a minLength error\n * @param minLength The minLength constraint\n * @param options The optional validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function minLengthError(\n minLength: number,\n options?: ValidationErrorOptions,\n): WithoutFieldTree;\nexport function minLengthError(\n minLength: number,\n options?: ValidationErrorOptions,\n): WithOptionalFieldTree {\n return new MinLengthValidationError(minLength, options);\n}\n\n/**\n * Create a maxLength error associated with the target field\n * @param maxLength The maxLength constraint\n * @param options The validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function maxLengthError(\n maxLength: number,\n options: WithFieldTree,\n): MaxLengthValidationError;\n/**\n * Create a maxLength error\n * @param maxLength The maxLength constraint\n * @param options The optional validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function maxLengthError(\n maxLength: number,\n options?: ValidationErrorOptions,\n): WithoutFieldTree;\nexport function maxLengthError(\n maxLength: number,\n options?: ValidationErrorOptions,\n): WithOptionalFieldTree {\n return new MaxLengthValidationError(maxLength, options);\n}\n\n/**\n * Create a pattern matching error associated with the target field\n * @param pattern The violated pattern\n * @param options The validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function patternError(\n pattern: RegExp,\n options: WithFieldTree,\n): PatternValidationError;\n/**\n * Create a pattern matching error\n * @param pattern The violated pattern\n * @param options The optional validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function patternError(\n pattern: RegExp,\n options?: ValidationErrorOptions,\n): WithoutFieldTree;\nexport function patternError(\n pattern: RegExp,\n options?: ValidationErrorOptions,\n): WithOptionalFieldTree {\n return new PatternValidationError(pattern, options);\n}\n\n/**\n * Create an email format error associated with the target field\n * @param options The validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function emailError(options: WithFieldTree): EmailValidationError;\n/**\n * Create an email format error\n * @param options The optional validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function emailError(\n options?: ValidationErrorOptions,\n): WithoutFieldTree;\nexport function emailError(\n options?: ValidationErrorOptions,\n): WithOptionalFieldTree {\n return new EmailValidationError(options);\n}\n\n/**\n * Common interface for all validation errors.\n *\n * This can be returned from validators.\n *\n * It's also used by the creation functions to create an instance\n * (e.g. `requiredError`, `minError`, etc.).\n *\n * @see [Signal Form Validation](guide/forms/signals/validation)\n * @see [Signal Form Validation Errors](guide/forms/signals/validation#validation-errors)\n * @category validation\n * @experimental 21.0.0\n */\nexport interface ValidationError {\n /** Identifies the kind of error. */\n readonly kind: string;\n /** Human readable error message. */\n readonly message?: string;\n}\n\nexport declare namespace ValidationError {\n /**\n * Validation error with an associated field tree.\n *\n * This is returned from field state, e.g., catField.errors() would be of a list of errors with\n * `field: catField` bound to state.\n */\n export interface WithFieldTree extends ValidationError {\n /** The field associated with this error. */\n readonly fieldTree: FieldTree;\n readonly formField?: FormField;\n }\n /** @deprecated Use `ValidationError.WithFieldTree` instead */\n export type WithField = WithFieldTree;\n\n /**\n * Validation error with an associated field tree and specific form field binding.\n */\n export interface WithFormField extends WithFieldTree {\n readonly formField: FormField;\n }\n\n /**\n * Validation error with optional field.\n *\n * This is generally used in places where the result might have a field.\n * e.g., as a result of a `validateTree`, or when handling form submission.\n */\n export interface WithOptionalFieldTree extends ValidationError {\n /** The field associated with this error. */\n readonly fieldTree?: FieldTree;\n }\n /** @deprecated Use `ValidationError.WithOptionalFieldTree` instead */\n export type WithOptionalField = WithOptionalFieldTree;\n\n /**\n * Validation error with no field.\n *\n * This is used to strongly enforce that fields are not allowed in validation result.\n */\n export interface WithoutFieldTree extends ValidationError {\n /** The field associated with this error. */\n readonly fieldTree?: never;\n readonly formField?: never;\n }\n /** @deprecated Use `ValidationError.WithoutFieldTree` instead */\n export type WithoutField = WithoutFieldTree;\n}\n\n/**\n * Internal version of `NgValidationError`, we create this separately so we can change its type on\n * the exported version to a type union of the possible sub-classes.\n *\n * @experimental 21.0.0\n */\nexport abstract class BaseNgValidationError implements ValidationError {\n /** Brand the class to avoid Typescript structural matching */\n private __brand = undefined;\n\n /** Identifies the kind of error. */\n readonly kind: string = '';\n\n /** The field associated with this error. */\n readonly fieldTree!: FieldTree;\n\n /** Human readable error message. */\n readonly message?: string;\n\n constructor(options?: ValidationErrorOptions) {\n if (options) {\n Object.assign(this, options);\n }\n }\n}\n\n/**\n * An error used to indicate that a required field is empty.\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport class RequiredValidationError extends BaseNgValidationError {\n override readonly kind = 'required';\n}\n\n/**\n * An error used to indicate that a value is lower than the minimum allowed.\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport class MinValidationError extends BaseNgValidationError {\n override readonly kind = 'min';\n\n constructor(\n readonly min: number,\n options?: ValidationErrorOptions,\n ) {\n super(options);\n }\n}\n\n/**\n * An error used to indicate that a value is higher than the maximum allowed.\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport class MaxValidationError extends BaseNgValidationError {\n override readonly kind = 'max';\n\n constructor(\n readonly max: number,\n options?: ValidationErrorOptions,\n ) {\n super(options);\n }\n}\n\n/**\n * An error used to indicate that a value is shorter than the minimum allowed length.\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport class MinLengthValidationError extends BaseNgValidationError {\n override readonly kind = 'minLength';\n\n constructor(\n readonly minLength: number,\n options?: ValidationErrorOptions,\n ) {\n super(options);\n }\n}\n\n/**\n * An error used to indicate that a value is longer than the maximum allowed length.\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport class MaxLengthValidationError extends BaseNgValidationError {\n override readonly kind = 'maxLength';\n\n constructor(\n readonly maxLength: number,\n options?: ValidationErrorOptions,\n ) {\n super(options);\n }\n}\n\n/**\n * An error used to indicate that a value does not match the required pattern.\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport class PatternValidationError extends BaseNgValidationError {\n override readonly kind = 'pattern';\n\n constructor(\n readonly pattern: RegExp,\n options?: ValidationErrorOptions,\n ) {\n super(options);\n }\n}\n\n/**\n * An error used to indicate that a value is not a valid email.\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport class EmailValidationError extends BaseNgValidationError {\n override readonly kind = 'email';\n}\n\n/**\n * An error used to indicate that a value entered in a native input does not parse.\n *\n * @category validation\n * @experimental 21.2.0\n */\nexport class NativeInputParseError extends BaseNgValidationError {\n override readonly kind = 'parse';\n}\n\n/**\n * The base class for all built-in, non-custom errors. This class can be used to check if an error\n * is one of the standard kinds, allowing you to switch on the kind to further narrow the type.\n *\n * @example\n * ```ts\n * const f = form(...);\n * for (const e of form().errors()) {\n * if (e instanceof NgValidationError) {\n * switch(e.kind) {\n * case 'required':\n * console.log('This is required!');\n * break;\n * case 'min':\n * console.log(`Must be at least ${e.min}`);\n * break;\n * ...\n * }\n * }\n * }\n * ```\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport const NgValidationError: abstract new () => NgValidationError = BaseNgValidationError as any;\nexport type NgValidationError =\n | RequiredValidationError\n | MinValidationError\n | MaxValidationError\n | MinLengthValidationError\n | MaxLengthValidationError\n | PatternValidationError\n | EmailValidationError\n | StandardSchemaValidationError\n | NativeInputParseError;\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\nimport {PathKind, SchemaPath, SchemaPathRules} from '../../types';\nimport {BaseValidatorConfig, getOption, isEmpty} from './util';\nimport {validate} from './validate';\nimport {emailError} from './validation_errors';\n\n/**\n * A regular expression that matches valid e-mail addresses.\n *\n * At a high level, this regexp matches e-mail addresses of the format `local-part@tld`, where:\n * - `local-part` consists of one or more of the allowed characters (alphanumeric and some\n * punctuation symbols).\n * - `local-part` cannot begin or end with a period (`.`).\n * - `local-part` cannot be longer than 64 characters.\n * - `tld` consists of one or more `labels` separated by periods (`.`). For example `localhost` or\n * `foo.com`.\n * - A `label` consists of one or more of the allowed characters (alphanumeric, dashes (`-`) and\n * periods (`.`)).\n * - A `label` cannot begin or end with a dash (`-`) or a period (`.`).\n * - A `label` cannot be longer than 63 characters.\n * - The whole address cannot be longer than 254 characters.\n *\n * ## Implementation background\n *\n * This regexp was ported over from AngularJS (see there for git history):\n * https://github.com/angular/angular.js/blob/c133ef836/src/ng/directive/input.js#L27\n * It is based on the\n * [WHATWG version](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) with\n * some enhancements to incorporate more RFC rules (such as rules related to domain names and the\n * lengths of different parts of the address). The main differences from the WHATWG version are:\n * - Disallow `local-part` to begin or end with a period (`.`).\n * - Disallow `local-part` length to exceed 64 characters.\n * - Disallow total address length to exceed 254 characters.\n *\n * See [this commit](https://github.com/angular/angular.js/commit/f3f5cf72e) for more details.\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\n/**\n * Binds a validator to the given path that requires the value to match the standard email format.\n * This function can only be called on string paths.\n *\n * @param path Path of the field to validate\n * @param config Optional, allows providing any of the following options:\n * - `error`: Custom validation error(s) to be used instead of the default `ValidationError.email()`\n * or a function that receives the `FieldContext` and returns custom validation error(s).\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @see [Signal Form Email Validation](guide/forms/signals/validation#email)\n * @category validation\n * @experimental 21.0.0\n */\nexport function email(\n path: SchemaPath,\n config?: BaseValidatorConfig,\n) {\n validate(path, (ctx) => {\n if (isEmpty(ctx.value())) {\n return undefined;\n }\n if (!EMAIL_REGEXP.test(ctx.value())) {\n if (config?.error) {\n return getOption(config.error, ctx);\n } else {\n return emailError({message: getOption(config?.message, ctx)});\n }\n }\n\n return undefined;\n });\n}\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\nimport {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../../types';\nimport {createMetadataKey, MAX, metadata} from '../metadata';\nimport {BaseValidatorConfig, getOption, isEmpty} from './util';\nimport {validate} from './validate';\nimport {maxError} from './validation_errors';\n\n/**\n * Binds a validator to the given path that requires the value to be less than or equal to the\n * given `maxValue`.\n * This function can only be called on number paths.\n * In addition to binding a validator, this function adds `MAX` property to the field.\n *\n * @param path Path of the field to validate\n * @param maxValue The maximum value, or a LogicFn that returns the maximum value.\n * @param config Optional, allows providing any of the following options:\n * - `error`: Custom validation error(s) to be used instead of the default `ValidationError.max(maxValue)`\n * or a function that receives the `FieldContext` and returns custom validation error(s).\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @see [Signal Form Max Validation](guide/forms/signals/validation#min-and-max)\n * @category validation\n * @experimental 21.0.0\n */\nexport function max(\n path: SchemaPath,\n maxValue: number | LogicFn,\n config?: BaseValidatorConfig,\n) {\n const MAX_MEMO = metadata(path, createMetadataKey(), (ctx) =>\n typeof maxValue === 'number' ? maxValue : maxValue(ctx),\n );\n metadata(path, MAX, ({state}) => state.metadata(MAX_MEMO)!());\n validate(path, (ctx) => {\n if (isEmpty(ctx.value())) {\n return undefined;\n }\n const max = ctx.state.metadata(MAX_MEMO)!();\n if (max === undefined || Number.isNaN(max)) {\n return undefined;\n }\n const value = ctx.value();\n const numValue = !value && value !== 0 ? NaN : Number(value); // Treat `''` and `null` as `NaN`\n if (numValue > max) {\n if (config?.error) {\n return getOption(config.error, ctx);\n } else {\n return maxError(max, {message: getOption(config?.message, ctx)});\n }\n }\n return undefined;\n });\n}\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\nimport {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../../types';\nimport {createMetadataKey, MAX_LENGTH, metadata} from '../metadata';\nimport {\n BaseValidatorConfig,\n getLengthOrSize,\n getOption,\n isEmpty,\n ValueWithLengthOrSize,\n} from './util';\nimport {validate} from './validate';\nimport {maxLengthError} from './validation_errors';\n\n/**\n * Binds a validator to the given path that requires the length of the value to be less than or\n * equal to the given `maxLength`.\n * This function can only be called on string or array paths.\n * In addition to binding a validator, this function adds `MAX_LENGTH` property to the field.\n *\n * @param path Path of the field to validate\n * @param maxLength The maximum length, or a LogicFn that returns the maximum length.\n * @param config Optional, allows providing any of the following options:\n * - `error`: Custom validation error(s) to be used instead of the default `ValidationError.maxLength(maxLength)`\n * or a function that receives the `FieldContext` and returns custom validation error(s).\n * @template TValue The type of value stored in the field the logic is bound to.\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @see [Signal Form Max Length Validation](guide/forms/signals/validation#minlength-and-maxlength)\n * @category validation\n * @experimental 21.0.0\n */\nexport function maxLength<\n TValue extends ValueWithLengthOrSize,\n TPathKind extends PathKind = PathKind.Root,\n>(\n path: SchemaPath,\n maxLength: number | LogicFn,\n config?: BaseValidatorConfig,\n) {\n const MAX_LENGTH_MEMO = metadata(path, createMetadataKey(), (ctx) =>\n typeof maxLength === 'number' ? maxLength : maxLength(ctx),\n );\n metadata(path, MAX_LENGTH, ({state}) => state.metadata(MAX_LENGTH_MEMO)!());\n validate(path, (ctx) => {\n if (isEmpty(ctx.value())) {\n return undefined;\n }\n const maxLength = ctx.state.metadata(MAX_LENGTH_MEMO)!();\n if (maxLength === undefined) {\n return undefined;\n }\n if (getLengthOrSize(ctx.value()) > maxLength) {\n if (config?.error) {\n return getOption(config.error, ctx);\n } else {\n return maxLengthError(maxLength, {message: getOption(config?.message, ctx)});\n }\n }\n return undefined;\n });\n}\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\nimport {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../../types';\nimport {createMetadataKey, metadata, MIN} from '../metadata';\nimport {BaseValidatorConfig, getOption, isEmpty} from './util';\nimport {validate} from './validate';\nimport {minError} from './validation_errors';\n\n/**\n * Binds a validator to the given path that requires the value to be greater than or equal to\n * the given `minValue`.\n * This function can only be called on number paths.\n * In addition to binding a validator, this function adds `MIN` property to the field.\n *\n * @param path Path of the field to validate\n * @param minValue The minimum value, or a LogicFn that returns the minimum value.\n * @param config Optional, allows providing any of the following options:\n * - `error`: Custom validation error(s) to be used instead of the default `ValidationError.min(minValue)`\n * or a function that receives the `FieldContext` and returns custom validation error(s).\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @see [Signal Form Min Validation](guide/forms/signals/validation#min-and-max)\n * @category validation\n * @experimental 21.0.0\n */\nexport function min<\n TValue extends number | string | null,\n TPathKind extends PathKind = PathKind.Root,\n>(\n path: SchemaPath,\n minValue: number | LogicFn,\n config?: BaseValidatorConfig,\n) {\n const MIN_MEMO = metadata(path, createMetadataKey(), (ctx) =>\n typeof minValue === 'number' ? minValue : minValue(ctx),\n );\n metadata(path, MIN, ({state}) => state.metadata(MIN_MEMO)!());\n validate(path, (ctx) => {\n if (isEmpty(ctx.value())) {\n return undefined;\n }\n const min = ctx.state.metadata(MIN_MEMO)!();\n if (min === undefined || Number.isNaN(min)) {\n return undefined;\n }\n const value = ctx.value();\n const numValue = !value && value !== 0 ? NaN : Number(value); // Treat `''` and `null` as `NaN`\n if (numValue < min) {\n if (config?.error) {\n return getOption(config.error, ctx);\n } else {\n return minError(min, {message: getOption(config?.message, ctx)});\n }\n }\n return undefined;\n });\n}\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\nimport {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../../types';\nimport {createMetadataKey, metadata, MIN_LENGTH} from '../metadata';\nimport {\n BaseValidatorConfig,\n getLengthOrSize,\n getOption,\n isEmpty,\n ValueWithLengthOrSize,\n} from './util';\nimport {validate} from './validate';\nimport {minLengthError} from './validation_errors';\n\n/**\n * Binds a validator to the given path that requires the length of the value to be greater than or\n * equal to the given `minLength`.\n * This function can only be called on string or array paths.\n * In addition to binding a validator, this function adds `MIN_LENGTH` property to the field.\n *\n * @param path Path of the field to validate\n * @param minLength The minimum length, or a LogicFn that returns the minimum length.\n * @param config Optional, allows providing any of the following options:\n * - `error`: Custom validation error(s) to be used instead of the default `ValidationError.minLength(minLength)`\n * or a function that receives the `FieldContext` and returns custom validation error(s).\n * @template TValue The type of value stored in the field the logic is bound to.\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @see [Signal Form Min Length Validation](guide/forms/signals/validation#minlength-and-maxlength)\n * @category validation\n * @experimental 21.0.0\n */\nexport function minLength<\n TValue extends ValueWithLengthOrSize,\n TPathKind extends PathKind = PathKind.Root,\n>(\n path: SchemaPath,\n minLength: number | LogicFn,\n config?: BaseValidatorConfig,\n) {\n const MIN_LENGTH_MEMO = metadata(path, createMetadataKey(), (ctx) =>\n typeof minLength === 'number' ? minLength : minLength(ctx),\n );\n metadata(path, MIN_LENGTH, ({state}) => state.metadata(MIN_LENGTH_MEMO)!());\n validate(path, (ctx) => {\n if (isEmpty(ctx.value())) {\n return undefined;\n }\n const minLength = ctx.state.metadata(MIN_LENGTH_MEMO)!();\n if (minLength === undefined) {\n return undefined;\n }\n if (getLengthOrSize(ctx.value()) < minLength) {\n if (config?.error) {\n return getOption(config.error, ctx);\n } else {\n return minLengthError(minLength, {message: getOption(config?.message, ctx)});\n }\n }\n return undefined;\n });\n}\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\nimport {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../../types';\nimport {createMetadataKey, metadata, PATTERN} from '../metadata';\nimport {BaseValidatorConfig, getOption, isEmpty} from './util';\nimport {validate} from './validate';\nimport {patternError} from './validation_errors';\n\n/**\n * Binds a validator to the given path that requires the value to match a specific regex pattern.\n * This function can only be called on string paths.\n * In addition to binding a validator, this function adds `PATTERN` property to the field.\n *\n * @param path Path of the field to validate\n * @param pattern The RegExp pattern to match, or a LogicFn that returns the RegExp pattern.\n * @param config Optional, allows providing any of the following options:\n * - `error`: Custom validation error(s) to be used instead of the default `ValidationError.pattern(pattern)`\n * or a function that receives the `FieldContext` and returns custom validation error(s).\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @see [Signal Form Pattern Validation](guide/forms/signals/validation#pattern)\n * @category validation\n * @experimental 21.0.0\n */\nexport function pattern(\n path: SchemaPath,\n pattern: RegExp | LogicFn,\n config?: BaseValidatorConfig,\n) {\n const PATTERN_MEMO = metadata(path, createMetadataKey(), (ctx) =>\n pattern instanceof RegExp ? pattern : pattern(ctx),\n );\n metadata(path, PATTERN, ({state}) => state.metadata(PATTERN_MEMO)!());\n validate(path, (ctx) => {\n if (isEmpty(ctx.value())) {\n return undefined;\n }\n const pattern = ctx.state.metadata(PATTERN_MEMO)!();\n if (pattern === undefined) {\n return undefined;\n }\n if (!pattern.test(ctx.value())) {\n if (config?.error) {\n return getOption(config.error, ctx);\n } else {\n return patternError(pattern, {message: getOption(config?.message, ctx)});\n }\n }\n return undefined;\n });\n}\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\nimport {LogicFn, PathKind, SchemaPath, SchemaPathRules} from '../../types';\nimport {createMetadataKey, metadata, REQUIRED} from '../metadata';\nimport {BaseValidatorConfig, getOption, isEmpty} from './util';\nimport {validate} from './validate';\nimport {requiredError} from './validation_errors';\n\n/**\n * Binds a validator to the given path that requires the value to be non-empty.\n * This function can only be called on any type of path.\n * In addition to binding a validator, this function adds `REQUIRED` property to the field.\n *\n * @param path Path of the field to validate\n * @param config Optional, allows providing any of the following options:\n * - `message`: A user-facing message for the error.\n * - `error`: Custom validation error(s) to be used instead of the default `ValidationError.required()`\n * or a function that receives the `FieldContext` and returns custom validation error(s).\n * - `when`: A function that receives the `FieldContext` and returns true if the field is required\n * @template TValue The type of value stored in the field the logic is bound to.\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @see [Signal Form Required Validation](guide/forms/signals/validation#required)\n * @category validation\n * @experimental 21.0.0\n */\nexport function required(\n path: SchemaPath,\n config?: BaseValidatorConfig & {\n when?: NoInfer>;\n },\n): void {\n const REQUIRED_MEMO = metadata(path, createMetadataKey(), (ctx) =>\n config?.when ? config.when(ctx) : true,\n );\n metadata(path, REQUIRED, ({state}) => state.metadata(REQUIRED_MEMO)!()!);\n validate(path, (ctx) => {\n if (ctx.state.metadata(REQUIRED_MEMO)!() && isEmpty(ctx.value())) {\n if (config?.error) {\n return getOption(config.error, ctx);\n } else {\n return requiredError({message: getOption(config?.message, ctx)});\n }\n }\n return undefined;\n });\n}\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\nimport {ResourceRef, Signal} from '@angular/core';\nimport {FieldNode} from '../../../field/node';\nimport {addDefaultField} from '../../../field/validation';\nimport {FieldPathNode} from '../../../schema/path_node';\nimport {assertPathIsCurrent} from '../../../schema/schema';\nimport {\n FieldContext,\n PathKind,\n SchemaPath,\n SchemaPathRules,\n TreeValidationResult,\n} from '../../types';\nimport {createManagedMetadataKey, metadata} from '../metadata';\n\n/**\n * A function that takes the result of an async operation and the current field context, and maps it\n * to a list of validation errors.\n *\n * @param result The result of the async operation.\n * @param ctx The context for the field the validator is attached to.\n * @return A validation error, or list of validation errors to report based on the result of the async operation.\n * The returned errors can optionally specify a field that the error should be targeted to.\n * A targeted error will show up as an error on its target field rather than the field being validated.\n * If a field is not given, the error is assumed to apply to the field being validated.\n * @template TValue The type of value stored in the field being validated.\n * @template TResult The type of result returned by the async operation\n * @template TPathKind The kind of path being validated (a root path, child path, or item of an array)\n *\n * @experimental 21.0.0\n */\nexport type MapToErrorsFn = (\n result: TResult,\n ctx: FieldContext,\n) => TreeValidationResult;\n\n/**\n * Options that indicate how to create a resource for async validation for a field,\n * and map its result to validation errors.\n *\n * @template TValue The type of value stored in the field being validated.\n * @template TParams The type of parameters to the resource.\n * @template TResult The type of result returned by the resource\n * @template TPathKind The kind of path being validated (a root path, child path, or item of an array)\n * @see [Signal Form Async Validation](guide/forms/signals/validation#async-validation)\n * @category validation\n * @experimental 21.0.0\n */\nexport interface AsyncValidatorOptions<\n TValue,\n TParams,\n TResult,\n TPathKind extends PathKind = PathKind.Root,\n> {\n /**\n * A function that receives the field context and returns the params for the resource.\n *\n * @param ctx The field context for the field being validated.\n * @returns The params for the resource.\n */\n readonly params: (ctx: FieldContext) => TParams;\n\n /**\n * A function that receives the resource params and returns a resource of the given params.\n * The given params should be used as is to create the resource.\n * The forms system will report the params as `undefined` when this validation doesn't need to be run.\n *\n * @param params The params to use for constructing the resource\n * @returns A reference to the constructed resource.\n */\n readonly factory: (params: Signal) => ResourceRef;\n /**\n * A function to handle errors thrown by httpResource (HTTP errors, network errors, etc.).\n * Receives the error and the field context, returns a list of validation errors.\n */\n readonly onError: (error: unknown, ctx: FieldContext) => TreeValidationResult;\n /**\n * A function that takes the resource result, and the current field context and maps it to a list\n * of validation errors.\n *\n * @param result The resource result.\n * @param ctx The context for the field the validator is attached to.\n * @return A validation error, or list of validation errors to report based on the resource result.\n * The returned errors can optionally specify a field that the error should be targeted to.\n * A targeted error will show up as an error on its target field rather than the field being validated.\n * If a field is not given, the error is assumed to apply to the field being validated.\n */\n readonly onSuccess: MapToErrorsFn;\n}\n\n/**\n * Adds async validation to the field corresponding to the given path based on a resource.\n * Async validation for a field only runs once all synchronous validation is passing.\n *\n * @param path A path indicating the field to bind the async validation logic to.\n * @param opts The async validation options.\n * @template TValue The type of value stored in the field being validated.\n * @template TParams The type of parameters to the resource.\n * @template TResult The type of result returned by the resource\n * @template TPathKind The kind of path being validated (a root path, child path, or item of an array)\n *\n * @see [Signal Form Async Validation](guide/forms/signals/validation#async-validation)\n * @category validation\n * @experimental 21.0.0\n */\nexport function validateAsync(\n path: SchemaPath,\n opts: AsyncValidatorOptions,\n): void {\n assertPathIsCurrent(path);\n const pathNode = FieldPathNode.unwrapFieldPath(path);\n\n const RESOURCE = createManagedMetadataKey, TParams | undefined>(\n opts.factory,\n );\n metadata(path, RESOURCE, (ctx) => {\n const node = ctx.stateOf(path) as FieldNode;\n const validationState = node.validationState;\n if (validationState.shouldSkipValidation() || !validationState.syncValid()) {\n return undefined;\n }\n return opts.params(ctx);\n });\n\n pathNode.builder.addAsyncErrorRule((ctx) => {\n const res = ctx.state.metadata(RESOURCE)!;\n let errors;\n switch (res.status()) {\n case 'idle':\n return undefined;\n case 'loading':\n case 'reloading':\n return 'pending';\n case 'resolved':\n case 'local':\n if (!res.hasValue()) {\n return undefined;\n }\n errors = opts.onSuccess(res.value()!, ctx as FieldContext);\n return addDefaultField(errors, ctx.fieldTree);\n case 'error':\n errors = opts.onError(res.error(), ctx as FieldContext);\n return addDefaultField(errors, ctx.fieldTree);\n }\n });\n}\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\nimport {addDefaultField} from '../../../field/validation';\nimport {FieldPathNode} from '../../../schema/path_node';\nimport {assertPathIsCurrent} from '../../../schema/schema';\nimport type {FieldContext, PathKind, SchemaPath, SchemaPathRules, TreeValidator} from '../../types';\n\n/**\n * Adds logic to a field to determine if the field or any of its child fields has validation errors.\n *\n * @param path The target path to add the validation logic to.\n * @param logic A `TreeValidator` that returns the current validation errors.\n * Errors returned by the validator may specify a target field to indicate an error on a child field.\n * @template TValue The type of value stored in the field the logic is bound to.\n * @template TPathKind The kind of path the logic is bound to (a root path, child path, or item of an array)\n *\n * @category logic\n * @experimental 21.0.0\n */\nexport function validateTree(\n path: SchemaPath,\n logic: NoInfer>,\n): void {\n assertPathIsCurrent(path);\n\n const pathNode = FieldPathNode.unwrapFieldPath(path);\n pathNode.builder.addSyncTreeErrorRule((ctx) =>\n addDefaultField(logic(ctx as FieldContext), ctx.fieldTree),\n );\n}\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\nimport {resource, ɵisPromise} from '@angular/core';\nimport type {StandardSchemaV1} from '@standard-schema/spec';\nimport {addDefaultField} from '../../../field/validation';\nimport type {FieldTree, LogicFn, SchemaPath, SchemaPathTree} from '../../types';\nimport {createMetadataKey, metadata} from '../metadata';\nimport {validateAsync} from './validate_async';\nimport {validateTree} from './validate_tree';\nimport {\n BaseNgValidationError,\n type ValidationErrorOptions,\n type WithFieldTree,\n type WithOptionalFieldTree,\n type WithoutFieldTree,\n} from './validation_errors';\n\n/**\n * Utility type that removes a string index key when its value is `unknown`,\n * i.e. `{[key: string]: unknown}`. It allows specific string keys to pass through, even if their\n * value is `unknown`, e.g. `{key: unknown}`.\n *\n * @experimental 21.0.0\n */\nexport type RemoveStringIndexUnknownKey = string extends K\n ? unknown extends V\n ? never\n : K\n : K;\n\n/**\n * Utility type that recursively ignores unknown string index properties on the given object.\n * We use this on the `TSchema` type in `validateStandardSchema` in order to accommodate Zod's\n * `looseObject` which includes `{[key: string]: unknown}` as part of the type.\n *\n * @experimental 21.0.0\n */\nexport type IgnoreUnknownProperties =\n T extends Record\n ? {\n [K in keyof T as RemoveStringIndexUnknownKey]: IgnoreUnknownProperties;\n }\n : T;\n\n/**\n * Validates a field using a `StandardSchemaV1` compatible validator (e.g. a Zod validator).\n *\n * See https://github.com/standard-schema/standard-schema for more about standard schema.\n *\n * @param path The `FieldPath` to the field to validate.\n * @param schema The standard schema compatible validator to use for validation, or a LogicFn that returns the schema.\n * @template TSchema The type validated by the schema. This may be either the full `TValue` type,\n * or a partial of it.\n * @template TValue The type of value stored in the field being validated.\n *\n * @see [Signal Form Schema Validation](guide/forms/signals/validation#integration-with-schema-validation-libraries)\n * @category validation\n * @experimental 21.0.0\n */\nexport function validateStandardSchema>(\n path: SchemaPath & SchemaPathTree,\n schema: StandardSchemaV1 | LogicFn | undefined>,\n) {\n // We create both a sync and async validator because the standard schema validator can return\n // either a sync result or a Promise, and we need to handle both cases. The sync validator\n // handles the sync result, and the async validator handles the Promise.\n // We memoize the result of the validation function here, so that it is only run once for both\n // validators, it can then be passed through both sync & async validation.\n type Result = StandardSchemaV1.Result | Promise>;\n const VALIDATOR_MEMO = metadata(\n path as SchemaPath,\n createMetadataKey(),\n (ctx) => {\n const resolvedSchema = typeof schema === 'function' ? schema(ctx) : schema;\n return resolvedSchema\n ? (resolvedSchema['~standard'].validate(ctx.value()) as Result)\n : undefined;\n },\n );\n\n validateTree(path, ({state, fieldTreeOf}) => {\n // Skip sync validation if the result is a Promise or undefined.\n const result = state.metadata(VALIDATOR_MEMO)!();\n if (!result || ɵisPromise(result)) {\n return [];\n }\n return (\n result?.issues?.map((issue) =>\n standardIssueToFormTreeError(fieldTreeOf(path), issue),\n ) ?? []\n );\n });\n\n validateAsync<\n TModel,\n Promise> | undefined,\n readonly StandardSchemaV1.Issue[]\n >(path, {\n params: ({state}) => {\n // Skip async validation if the result is *not* a Promise.\n const result = state.metadata(VALIDATOR_MEMO)!();\n return result && ɵisPromise(result) ? result : undefined;\n },\n factory: (params) => {\n return resource({\n params,\n loader: async ({params}) => (await params)?.issues ?? [],\n });\n },\n onSuccess: (issues, {fieldTreeOf}) => {\n return issues.map((issue) => standardIssueToFormTreeError(fieldTreeOf(path), issue));\n },\n onError: () => {},\n });\n}\n\n/**\n * Create a standard schema issue error associated with the target field\n * @param issue The standard schema issue\n * @param options The validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function standardSchemaError(\n issue: StandardSchemaV1.Issue,\n options: WithFieldTree,\n): StandardSchemaValidationError;\n/**\n * Create a standard schema issue error\n * @param issue The standard schema issue\n * @param options The optional validation error options\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport function standardSchemaError(\n issue: StandardSchemaV1.Issue,\n options?: ValidationErrorOptions,\n): WithoutFieldTree;\nexport function standardSchemaError(\n issue: StandardSchemaV1.Issue,\n options?: ValidationErrorOptions,\n): WithOptionalFieldTree {\n return new StandardSchemaValidationError(issue, options);\n}\n\n/**\n * Converts a `StandardSchemaV1.Issue` to a `FormTreeError`.\n *\n * @param fieldTree The root field to which the issue's path is relative.\n * @param issue The `StandardSchemaV1.Issue` to convert.\n * @returns A `ValidationError` representing the issue.\n */\nfunction standardIssueToFormTreeError(\n fieldTree: FieldTree,\n issue: StandardSchemaV1.Issue,\n): StandardSchemaValidationError {\n let target = fieldTree as FieldTree>;\n for (const pathPart of issue.path ?? []) {\n const pathKey = typeof pathPart === 'object' ? pathPart.key : pathPart;\n target = target[pathKey] as FieldTree>;\n }\n return addDefaultField(standardSchemaError(issue, {message: issue.message}), target);\n}\n\n/**\n * An error used to indicate an issue validating against a standard schema.\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport class StandardSchemaValidationError extends BaseNgValidationError {\n override readonly kind = 'standardSchema';\n\n constructor(\n readonly issue: StandardSchemaV1.Issue,\n options?: ValidationErrorOptions,\n ) {\n super(options);\n }\n}\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\nimport {httpResource, HttpResourceOptions, HttpResourceRequest} from '@angular/common/http';\nimport {Signal} from '@angular/core';\nimport {\n FieldContext,\n SchemaPath,\n PathKind,\n TreeValidationResult,\n SchemaPathRules,\n} from '../../types';\nimport {MapToErrorsFn, validateAsync} from './validate_async';\n\n/**\n * Options that indicate how to create an httpResource for async validation for a field,\n * and map its result to validation errors.\n *\n * @template TValue The type of value stored in the field being validated.\n * @template TResult The type of result returned by the httpResource\n * @template TPathKind The kind of path being validated (a root path, child path, or item of an array)\n *\n * @category validation\n * @experimental 21.0.0\n */\nexport interface HttpValidatorOptions {\n /**\n * A function that receives the field context and returns the url or request for the httpResource.\n * If given a URL, the underlying httpResource will perform an HTTP GET on it.\n *\n * @param ctx The field context for the field being validated.\n * @returns The URL or request for creating the httpResource.\n */\n readonly request:\n | ((ctx: FieldContext) => string | undefined)\n | ((ctx: FieldContext) => HttpResourceRequest | undefined);\n\n /**\n * A function that takes the httpResource result, and the current field context and maps it to a\n * list of validation errors.\n *\n * @param result The httpResource result.\n * @param ctx The context for the field the validator is attached to.\n * @return A validation error, or list of validation errors to report based on the httpResource result.\n * The returned errors can optionally specify a field that the error should be targeted to.\n * A targeted error will show up as an error on its target field rather than the field being validated.\n * If a field is not given, the error is assumed to apply to the field being validated.\n */\n readonly onSuccess: MapToErrorsFn;\n\n /**\n * A function to handle errors thrown by httpResource (HTTP errors, network errors, etc.).\n * Receives the error and the field context, returns a list of validation errors.\n */\n readonly onError: (error: unknown, ctx: FieldContext) => TreeValidationResult;\n /**\n * The options to use when creating the httpResource.\n */\n readonly options?: HttpResourceOptions;\n}\n\n/**\n * Adds async validation to the field corresponding to the given path based on an httpResource.\n * Async validation for a field only runs once all synchronous validation is passing.\n *\n * @param path A path indicating the field to bind the async validation logic to.\n * @param opts The http validation options.\n * @template TValue The type of value stored in the field being validated.\n * @template TResult The type of result returned by the httpResource\n * @template TPathKind The kind of path being validated (a root path, child path, or item of an array)\n *\n * @see [Signal Form Async Validation](guide/forms/signals/validation#async-validation)\n * @category validation\n * @experimental 21.0.0\n */\nexport function validateHttp(\n path: SchemaPath,\n opts: HttpValidatorOptions,\n) {\n validateAsync(path, {\n params: opts.request,\n factory: (request: Signal) => httpResource(request, opts.options),\n onSuccess: opts.onSuccess,\n onError: opts.onError,\n });\n}\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\nimport {DEBOUNCER} from '../../field/debounce';\nimport {FieldPathNode} from '../../schema/path_node';\nimport {assertPathIsCurrent} from '../../schema/schema';\nimport type {Debouncer, PathKind, SchemaPath, SchemaPathRules} from '../types';\n\n/**\n * Configures the frequency at which a form field is updated by UI events.\n *\n * When this rule is applied, updates from the UI to the form model will be delayed until either\n * the field is touched, or the most recently debounced update resolves.\n *\n * @param path The target path to debounce.\n * @param durationOrDebouncer Either a debounce duration in milliseconds, or a custom\n * {@link Debouncer} function.\n *\n * @experimental 21.0.0\n */\nexport function debounce(\n path: SchemaPath,\n durationOrDebouncer: number | Debouncer,\n): void {\n assertPathIsCurrent(path);\n\n const pathNode = FieldPathNode.unwrapFieldPath(path);\n const debouncer =\n typeof durationOrDebouncer === 'function'\n ? durationOrDebouncer\n : durationOrDebouncer > 0\n ? debounceForDuration(durationOrDebouncer)\n : immediate;\n pathNode.builder.addMetadataRule(DEBOUNCER, () => debouncer);\n}\n\nfunction debounceForDuration(durationInMilliseconds: number): Debouncer {\n return (_context, abortSignal) => {\n return new Promise((resolve) => {\n let timeoutId: ReturnType | undefined;\n\n const onAbort = () => {\n clearTimeout(timeoutId);\n resolve();\n };\n\n timeoutId = setTimeout(() => {\n abortSignal.removeEventListener('abort', onAbort);\n resolve();\n }, durationInMilliseconds);\n\n abortSignal.addEventListener('abort', onAbort, {once: true});\n });\n };\n}\n\nfunction immediate() {}\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\nimport {InjectionToken, type Signal, type WritableSignal} from '@angular/core';\nimport type {ValidationError} from '../api/rules';\n\n/**\n * DI token that provides a writable signal that controls can use to set the signal of parse errors\n * for the `FormField` directive. Used internally by `transformedValue`.\n *\n * @experimental 21.2.0\n */\nexport const FORM_FIELD_PARSE_ERRORS = new InjectionToken<\n WritableSignal | undefined>\n>(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FORM_FIELD_PARSE_ERRORS' : '');\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\nimport {type Signal, linkedSignal} from '@angular/core';\nimport type {ValidationError} from '../api/rules';\nimport {normalizeErrors} from '../api/rules/validation/util';\nimport type {ParseResult} from '../api/transformed_value';\n\n/**\n * An object that handles parsing raw UI values into model values.\n */\nexport interface Parser {\n /**\n * Errors encountered during the last parse attempt.\n */\n errors: Signal;\n /**\n * Parses the given raw value and updates the underlying model value if successful.\n */\n setRawValue: (rawValue: TRaw) => void;\n}\n\n/**\n * Creates a {@link Parser} that synchronizes a raw value with an underlying model value.\n *\n * @param getValue Function to get the current model value.\n * @param setValue Function to update the model value.\n * @param parse Function to parse the raw value into a {@link ParseResult}.\n * @returns A {@link Parser} instance.\n */\nexport function createParser(\n getValue: () => TValue,\n setValue: (value: TValue) => void,\n parse: (raw: TRaw) => ParseResult,\n): Parser {\n const errors = linkedSignal({\n source: getValue,\n computation: () => [] as readonly ValidationError.WithoutFieldTree[],\n });\n\n const setRawValue = (rawValue: TRaw) => {\n const result = parse(rawValue);\n errors.set(normalizeErrors(result.error));\n if (result.value !== undefined) {\n setValue(result.value);\n }\n // `errors` is a linked signal sourced from the model value; write parse errors after\n // model updates so `{value, errors}` results do not get reset by the recomputation.\n errors.set(normalizeErrors(result.error));\n };\n\n return {errors: errors.asReadonly(), setRawValue};\n}\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\nimport {\n inject,\n linkedSignal,\n type ModelSignal,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport {FORM_FIELD_PARSE_ERRORS} from '../directive/parse_errors';\nimport {createParser} from '../util/parser';\nimport type {ValidationError} from './rules';\nimport type {OneOrMany} from './types';\n\n/**\n * Result of parsing a raw value into a model value.\n */\nexport interface ParseResult {\n /**\n * The parsed value. If omitted, the model is not updated.\n */\n readonly value?: TValue;\n /**\n * Errors encountered during parsing, if any.\n */\n readonly error?: OneOrMany;\n}\n\n/**\n * Options for `transformedValue`.\n *\n * @experimental 21.2.0\n */\nexport interface TransformedValueOptions {\n /**\n * Parse the raw value into the model value.\n *\n * Should return an object containing the parsed result, which may contain:\n * - `value`: The parsed model value. If `undefined`, the model will not be updated.\n * - `error`: Any parse errors encountered. If `undefined`, no errors are reported.\n */\n parse: (rawValue: TRaw) => ParseResult;\n\n /**\n * Format the model value into the raw value.\n */\n format: (value: TValue) => TRaw;\n}\n\n/**\n * A writable signal representing a \"raw\" UI value that is synchronized with a model signal\n * via parse/format transformations.\n *\n * @category control\n * @experimental 21.2.0\n */\nexport interface TransformedValueSignal extends WritableSignal {\n /**\n * The current parse errors resulting from the last transformation.\n */\n readonly parseErrors: Signal;\n}\n\n/**\n * Creates a writable signal representing a \"raw\" UI value that is transformed to/from a model\n * value via `parse` and `format` functions.\n *\n * This utility simplifies the creation of custom form controls that parse a user-facing value\n * representation into an underlying model value. For example, a numeric input that displays and\n * accepts string values but stores a number.\n *\n * Parse errors are exposed via the returned signal’s `parseErrors()` property.\n * When `transformedValue` is used within a Signal Forms field context, parse errors are also\n * reported to the nearest field automatically. When no field context is present, no automatic\n * reporting occurs and `parseErrors` can be consumed directly.\n *\n * Note: `parse` may return both a `value` and an `error`. Returning `value` updates the model;\n * omitting it leaves the model unchanged.\n *\n * @param value The model signal to synchronize with.\n * @param options Configuration including `parse` and `format` functions.\n * @returns A `TransformedValueSignal` representing the raw value with parse error tracking.\n * @experimental 21.2.0\n *\n * @example\n * ```ts\n * @Component({\n * selector: 'number-input',\n * template: ``,\n * })\n * export class NumberInput implements FormValueControl {\n * readonly value = model.required();\n *\n * protected readonly rawValue = transformedValue(this.value, {\n * parse: (val) => {\n * if (val === '') return {value: null};\n * const num = Number(val);\n * if (Number.isNaN(num)) {\n * return {error: {kind: 'parse', message: `${val} is not numeric`}};\n * }\n * return {value: num};\n * },\n * format: (val) => val?.toString() ?? '',\n * });\n * }\n * ```\n */\nexport function transformedValue(\n value: ModelSignal,\n options: TransformedValueOptions,\n): TransformedValueSignal {\n const {parse, format} = options;\n const parser = createParser(value, value.set, parse);\n\n // Wire up the parse errors from the parser to the form field.\n const formFieldParseErrors = inject(FORM_FIELD_PARSE_ERRORS, {self: true, optional: true});\n if (formFieldParseErrors) {\n formFieldParseErrors.set(parser.errors);\n }\n\n // Create the result signal with overridden set/update and a `parseErrors` property.\n const rawValue = linkedSignal(() => format(value()));\n const result = rawValue as WritableSignal & {\n parseErrors: Signal;\n };\n result.parseErrors = parser.errors;\n const originalSet = result.set.bind(result);\n\n // Notify the parser when `set` or `update` is called on the raw value\n result.set = (newRawValue: TRaw) => {\n parser.setRawValue(newRawValue);\n originalSet(newRawValue);\n };\n result.update = (updateFn: (value: TRaw) => TRaw) => {\n result.set(updateFn(rawValue()));\n };\n\n return result;\n}\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\nimport {ɵRuntimeError as RuntimeError} from '@angular/core';\nimport {RuntimeErrorCode} from '../errors';\nimport {signalErrorsToValidationErrors} from '../compat/validation_errors';\n\nimport {\n ControlValueAccessor,\n Validators,\n type AbstractControl,\n type FormControlStatus,\n type ValidationErrors,\n type ValidatorFn,\n} from '@angular/forms';\nimport type {FieldState} from '../api/types';\n\n// TODO: Also consider supporting (if possible):\n// - hasError\n// - getError\n// - reset\n// - name\n// - path\n// - markAs[Touched,Dirty,etc.]\n\n/**\n * Represents a combination of `NgControl` and `AbstractControl`.\n *\n * Note: We have this separate interface, rather than implementing the relevant parts of the two\n * controls with something like `InteropNgControl implements Pick, Pick`\n * because it confuses the internal JS minifier which can cause collisions in field names.\n */\ninterface CombinedControl {\n value: any;\n valid: boolean;\n invalid: boolean;\n touched: boolean;\n untouched: boolean;\n disabled: boolean;\n enabled: boolean;\n errors: ValidationErrors | null;\n pristine: boolean;\n dirty: boolean;\n status: FormControlStatus;\n control: AbstractControl;\n valueAccessor: ControlValueAccessor | null;\n hasValidator(validator: ValidatorFn): boolean;\n updateValueAndValidity(): void;\n}\n\n/**\n * A fake version of `NgControl` provided by the `Field` directive. This allows interoperability\n * with a wider range of components designed to work with reactive forms, in particular ones that\n * inject the `NgControl`. The interop control does not implement *all* properties and methods of\n * the real `NgControl`, but does implement some of the most commonly used ones that have a clear\n * equivalent in signal forms.\n */\nexport class InteropNgControl implements CombinedControl {\n constructor(protected field: () => FieldState) {}\n\n readonly control: AbstractControl = this as unknown as AbstractControl;\n\n get value(): any {\n return this.field().value();\n }\n\n get valid(): boolean {\n return this.field().valid();\n }\n\n get invalid(): boolean {\n return this.field().invalid();\n }\n\n get pending(): boolean | null {\n return this.field().pending();\n }\n\n get disabled(): boolean {\n return this.field().disabled();\n }\n\n get enabled(): boolean {\n return !this.field().disabled();\n }\n\n get errors(): ValidationErrors | null {\n return signalErrorsToValidationErrors(this.field().errors());\n }\n\n get pristine(): boolean {\n return !this.field().dirty();\n }\n\n get dirty(): boolean {\n return this.field().dirty();\n }\n\n get touched(): boolean {\n return this.field().touched();\n }\n\n get untouched(): boolean {\n return !this.field().touched();\n }\n\n get status(): FormControlStatus {\n if (this.field().disabled()) {\n return 'DISABLED';\n }\n if (this.field().valid()) {\n return 'VALID';\n }\n if (this.field().invalid()) {\n return 'INVALID';\n }\n if (this.field().pending()) {\n return 'PENDING';\n }\n throw new RuntimeError(\n RuntimeErrorCode.UNKNOWN_STATUS,\n ngDevMode && 'Unknown form control status',\n );\n }\n\n valueAccessor: ControlValueAccessor | null = null;\n\n hasValidator(validator: ValidatorFn): boolean {\n // This addresses a common case where users look for the presence of `Validators.required` to\n // determine whether or not to show a required \"*\" indicator in the UI.\n if (validator === Validators.required) {\n return this.field().required();\n }\n return false;\n }\n\n updateValueAndValidity() {\n // No-op since value and validity are always up to date in signal forms.\n // We offer this method so that reactive forms code attempting to call it doesn't error.\n }\n}\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\nimport type {FieldState} from '../api/types';\n\n/**\n * Branded type for the public name of an input we bind on control components or DOM elements.\n */\nexport type ControlBindingKey = string & {__brand: 'ControlBindingKey'};\n\n/**\n * A map of field state properties to control binding name.\n *\n * This excludes `controlValue` whose corresponding control binding name differs between control\n * types.\n *\n * The control binding name can be used for inputs or attributes (since DOM attributes are case\n * insensitive).\n */\nconst FIELD_STATE_KEY_TO_CONTROL_BINDING = {\n disabled: 'disabled' as ControlBindingKey,\n disabledReasons: 'disabledReasons' as ControlBindingKey,\n dirty: 'dirty' as ControlBindingKey,\n errors: 'errors' as ControlBindingKey,\n hidden: 'hidden' as ControlBindingKey,\n invalid: 'invalid' as ControlBindingKey,\n max: 'max' as ControlBindingKey,\n maxLength: 'maxLength' as ControlBindingKey,\n min: 'min' as ControlBindingKey,\n minLength: 'minLength' as ControlBindingKey,\n name: 'name' as ControlBindingKey,\n pattern: 'pattern' as ControlBindingKey,\n pending: 'pending' as ControlBindingKey,\n readonly: 'readonly' as ControlBindingKey,\n required: 'required' as ControlBindingKey,\n touched: 'touched' as ControlBindingKey,\n} as const satisfies {[K in keyof FieldState]?: ControlBindingKey};\n\n/**\n * Inverts `FIELD_STATE_KEY_TO_CONTROL_BINDING` to look up the minified name of the corresponding\n * field state property from its control binding name.\n */\nconst CONTROL_BINDING_TO_FIELD_STATE_KEY = /* @__PURE__ */ (() => {\n const map = {} as Record;\n for (const key of Object.keys(FIELD_STATE_KEY_TO_CONTROL_BINDING) as Array<\n keyof typeof FIELD_STATE_KEY_TO_CONTROL_BINDING\n >) {\n map[FIELD_STATE_KEY_TO_CONTROL_BINDING[key]] = key;\n }\n return map;\n})();\n\nexport function readFieldStateBindingValue(\n fieldState: FieldState,\n key: ControlBindingKey,\n): unknown {\n const property = CONTROL_BINDING_TO_FIELD_STATE_KEY[key];\n return fieldState[property]?.();\n}\n\n/** The keys of {@link FIELD_STATE_KEY_TO_CONTROL_BINDING} */\nexport const CONTROL_BINDING_NAMES = /* @__PURE__ */ (() =>\n Object.values(FIELD_STATE_KEY_TO_CONTROL_BINDING))() as Array;\n\nexport function createBindings(): {[K in TKey]?: unknown} {\n return {};\n}\n\nexport function bindingUpdated(\n bindings: {[K in TKey]?: unknown},\n key: TKey,\n value: unknown,\n) {\n if (bindings[key] !== value) {\n bindings[key] = value;\n return true;\n }\n return false;\n}\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\nimport {type Renderer2, untracked} from '@angular/core';\nimport {NativeInputParseError, WithoutFieldTree} from '../api/rules';\nimport type {ParseResult} from '../api/transformed_value';\n\n/**\n * Supported native control element types.\n *\n * The `type` property of a {@link HTMLTextAreaElement} should always be 'textarea', but the\n * TypeScript DOM API type definition lacks this detail, so we include it here.\n *\n * https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement/type\n */\nexport type NativeFormControl =\n | HTMLInputElement\n | HTMLSelectElement\n | (HTMLTextAreaElement & {type: 'textarea'});\n\nexport function isNativeFormElement(element: HTMLElement): element is NativeFormControl {\n return (\n element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA'\n );\n}\n\nexport function isNumericFormElement(element: HTMLElement): boolean {\n if (element.tagName !== 'INPUT') {\n return false;\n }\n\n const type = (element as HTMLInputElement).type;\n return (\n type === 'date' ||\n type === 'datetime-local' ||\n type === 'month' ||\n type === 'number' ||\n type === 'range' ||\n type === 'time' ||\n type === 'week'\n );\n}\n\nexport function isTextualFormElement(element: HTMLElement): boolean {\n return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';\n}\n\n/**\n * Returns the value from a native control element.\n *\n * @param element The native control element.\n * @param currentValue A function that returns the current value from the control's corresponding\n * field state.\n *\n * The type of the returned value depends on the `type` property of the control, and will attempt to\n * match the current value's type. For example, the value of `` can be read as\n * a `string` or a `number`. If the current value is a `number`, then this will return a `number`.\n * Otherwise, this will return the value as a `string`.\n */\nexport function getNativeControlValue(\n element: NativeFormControl,\n currentValue: () => unknown,\n): ParseResult {\n let modelValue: unknown;\n\n if (element.validity.badInput) {\n return {\n error: new NativeInputParseError() as WithoutFieldTree,\n };\n }\n\n // Special cases for specific input types.\n switch (element.type) {\n case 'checkbox':\n return {value: element.checked};\n case 'number':\n case 'range':\n case 'datetime-local':\n // We can read a `number` or a `string` from this input type. Prefer whichever is consistent\n // with the current type.\n modelValue = untracked(currentValue);\n if (typeof modelValue === 'number' || modelValue === null) {\n return {value: element.value === '' ? null : element.valueAsNumber};\n }\n break;\n case 'date':\n case 'month':\n case 'time':\n case 'week':\n // We can read a `Date | null`, `number`, or `string` from this input type. Prefer whichever\n // is consistent with the current type.\n modelValue = untracked(currentValue);\n if (modelValue === null || modelValue instanceof Date) {\n return {value: element.valueAsDate};\n } else if (typeof modelValue === 'number') {\n return {value: element.valueAsNumber};\n }\n break;\n }\n\n // Default to reading the value as a string.\n return {value: element.value};\n}\n\n/**\n * Sets a native control element's value.\n *\n * @param element The native control element.\n * @param value The new value to set.\n */\nexport function setNativeControlValue(element: NativeFormControl, value: unknown) {\n // Special cases for specific input types.\n switch (element.type) {\n case 'checkbox':\n element.checked = value as boolean;\n return;\n case 'radio':\n // Although HTML behavior is to clear the input already, we do this just in case. It seems\n // like it might be necessary in certain environments (e.g. Domino).\n element.checked = value === element.value;\n return;\n case 'number':\n case 'range':\n case 'datetime-local':\n // This input type can receive a `number` or a `string`.\n if (typeof value === 'number') {\n setNativeNumberControlValue(element, value);\n return;\n } else if (value === null) {\n element.value = '';\n return;\n }\n break;\n case 'date':\n case 'month':\n case 'time':\n case 'week':\n // This input type can receive a `Date | null` or a `number` or a `string`.\n if (value === null || value instanceof Date) {\n element.valueAsDate = value;\n return;\n } else if (typeof value === 'number') {\n setNativeNumberControlValue(element, value);\n return;\n }\n }\n\n // Default to setting the value as a string.\n element.value = value as string;\n}\n\n/** Writes a value to a native . */\nexport function setNativeNumberControlValue(element: HTMLInputElement, value: number) {\n // Writing `NaN` causes a warning in the console, so we instead write `''`.\n // This allows the user to safely use `NaN` as a number value that means \"clear the input\".\n if (isNaN(value)) {\n element.value = '';\n } else {\n element.valueAsNumber = value;\n }\n}\n\n/**\n * Updates the native DOM property on the given node.\n *\n * @param key The control binding key (identifies the property type, e.g. disabled, required).\n * @param name The DOM attribute/property name.\n * @param value The new value for the property.\n */\nexport function setNativeDomProperty(\n renderer: Renderer2,\n element: NativeFormControl,\n name: 'name' | 'disabled' | 'required' | 'readonly' | 'min' | 'max' | 'minLength' | 'maxLength',\n value: string | number | undefined,\n) {\n switch (name) {\n case 'name':\n renderer.setAttribute(element, name, value as string);\n break;\n case 'disabled':\n case 'readonly':\n case 'required':\n if (value) {\n renderer.setAttribute(element, name, '');\n } else {\n renderer.removeAttribute(element, name);\n }\n break;\n case 'max':\n case 'min':\n case 'minLength':\n case 'maxLength':\n if (value !== undefined) {\n renderer.setAttribute(element, name, value.toString());\n } else {\n renderer.removeAttribute(element, name);\n }\n break;\n }\n}\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\nimport type {ɵControlDirectiveHost as ControlDirectiveHost} from '@angular/core';\nimport type {FormField} from './form_field_directive';\nimport {\n bindingUpdated,\n CONTROL_BINDING_NAMES,\n type ControlBindingKey,\n createBindings,\n readFieldStateBindingValue,\n} from './bindings';\nimport {setNativeDomProperty} from './native';\nimport {FormUiControl} from '../api/control';\n\nexport function customControlCreate(\n host: ControlDirectiveHost,\n parent: FormField,\n): () => void {\n host.listenToCustomControlModel((value) => parent.state().controlValue.set(value));\n host.listenToCustomControlOutput('touchedChange', () => parent.state().markAsTouched());\n\n parent.registerAsBinding(host.customControl as FormUiControl);\n\n const bindings = createBindings();\n return () => {\n const state = parent.state();\n // Bind custom form control model ('value' or 'checked').\n const controlValue = state.controlValue();\n if (bindingUpdated(bindings, 'controlValue', controlValue)) {\n host.setCustomControlModelInput(controlValue);\n }\n\n // Bind remaining field state properties.\n for (const name of CONTROL_BINDING_NAMES) {\n let value: unknown;\n if (name === 'errors') {\n value = parent.errors();\n } else {\n value = readFieldStateBindingValue(state, name);\n }\n if (bindingUpdated(bindings, name, value)) {\n host.setInputOnDirectives(name, value);\n\n // If the host node is a native control, we can bind field state properties to native\n // properties for any that weren't defined as inputs on the custom control.\n if (parent.elementAcceptsNativeProperty(name) && !host.customControlHasInput(name)) {\n setNativeDomProperty(\n parent.renderer,\n parent.nativeFormElement!,\n name,\n value as string | number | undefined,\n );\n }\n }\n }\n };\n}\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\nimport {untracked, type ɵControlDirectiveHost as ControlDirectiveHost} from '@angular/core';\nimport {\n bindingUpdated,\n CONTROL_BINDING_NAMES,\n type ControlBindingKey,\n createBindings,\n readFieldStateBindingValue,\n} from './bindings';\nimport {setNativeDomProperty} from './native';\nimport type {FormField} from './form_field_directive';\n\nexport function cvaControlCreate(\n host: ControlDirectiveHost,\n parent: FormField,\n): () => void {\n parent.controlValueAccessor!.registerOnChange((value: unknown) =>\n parent.state().controlValue.set(value as any),\n );\n parent.controlValueAccessor!.registerOnTouched(() => parent.state().markAsTouched());\n parent.registerAsBinding();\n\n const bindings = createBindings();\n return () => {\n const fieldState = parent.state();\n const value = fieldState.value();\n if (bindingUpdated(bindings, 'controlValue', value)) {\n // We don't know if the interop control has underlying signals, so we must use `untracked` to\n // prevent writing to a signal in a reactive context.\n untracked(() => parent.controlValueAccessor!.writeValue(value));\n }\n\n for (const name of CONTROL_BINDING_NAMES) {\n const value = readFieldStateBindingValue(fieldState, name);\n if (bindingUpdated(bindings, name, value)) {\n const propertyWasSet = host.setInputOnDirectives(name, value);\n if (name === 'disabled' && parent.controlValueAccessor!.setDisabledState) {\n untracked(() => parent.controlValueAccessor!.setDisabledState!(value as boolean));\n } else if (!propertyWasSet && parent.elementAcceptsNativeProperty(name)) {\n // Fall back to native DOM properties.\n setNativeDomProperty(\n parent.renderer,\n parent.nativeFormElement,\n name,\n value as string | number | undefined,\n );\n }\n }\n }\n };\n}\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\nimport type {DestroyRef} from '@angular/core';\n\n/**\n * Creates a `MutationObserver` to observe changes to the available `