198 Commits

Author SHA1 Message Date
SkyZeroZx 7be9d5d6b2 refactor(forms): modernize signal forms tests to rely on whenStable
Rely on zoneless test scheduling instead of manually triggering change detection. Keep Signals Forms tests aligned with the async-first testing pattern.

(cherry picked from commit 59f6ef690b)
2026-07-29 09:57:42 -07:00
Kristiyan Kostadinov c836bcc693 fix(forms): add utility to assert that value is a field tree
Adds the `isFieldTree` utility that allows users to assert whether a value is a field tree. This is something that has come up on Material recently and will be useful for users as well.

Fixes #69984.

(cherry picked from commit 2a141847a5)
2026-07-29 08:53:28 -07:00
Matthieu Riegler d302c7ab83 fix(forms): ensure pending status propagates to the root form in signal forms
Previously, the `pending()` status on a field's `ValidationState` only checked if the field itself or its immediate children had a pending asynchronous validator by directly inspecting `asyncErrors()`. This meant that a pending asynchronous validator deep within a nested form (e.g. on a grand-child) would not correctly bubble the `pending` state up to the root form.

fixes #69840

(cherry picked from commit 3497c9b943)
2026-07-21 13:48:37 +02:00
Pawel Kozlowski e14ead1093 Revert "fix(forms): preserve intermediate number values in signal forms"
This reverts commit 64d6d47a0c.
2026-07-20 10:40:53 +02:00
Jaime Burgos 64d6d47a0c fix(forms): preserve intermediate number values in signal forms
Preserve raw native input text while editing so parsed model values are not written back on every keystroke.

(cherry picked from commit 2e32872720)
2026-07-15 15:56:52 -07:00
cexbrayat 70500e4067 fix(core): preserve explicit input transform write type
If a directive has an input declared as `dismissible = input<boolean>(true, {transform: booleanAttribute});` then the following templates were not compiling:

```
<div directiveName dismissible="true"></div>
<div directiveName dismissible></div>
```

This commit fixes the issue, without breaking contravariant consumers.

(cherry picked from commit 0ae6d81ed2)
2026-07-15 12:02:23 -07:00
Doug Parker 5936ffb80c refactor(core): update WebMCP tool registration to be asynchronous
In the latest WebMCP specification and Chromium preview builds, `document.modelContext.registerTool` was updated to be asynchronous and return a `Promise`: https://groups.google.com/a/chromium.org/g/chrome-ai-dev-preview/c/xQWt0b1sZIE/m/UJznbNCIAwAJ?utm_medium=email&utm_source=footer

This commit update adjusts Angular's experimental WebMCP implementation (`declareExperimentalWebMcpTool` and form registration) to be async as well, returning `Promise<void>`.

(cherry picked from commit f908140d71)
2026-07-10 10:56:45 -07:00
Bhuvansh855 5d06fcb109 test(forms): register writeback test control as CVA
Register the custom writeback test control as an NG_VALUE_ACCESSOR
so it is recognized as a valid formField host during debounce
writeback testing.

(cherry picked from commit 12fe700ad8)
2026-07-09 12:29:11 -07:00
Bhuvansh855 6cf7446afa fix(forms): prevent stale CVA writeback during debounce
Use controlValue() instead of value() when synchronizing
ControlValueAccessor instances.

When debounce is active, value() can still contain the
previous model value while controlValue() reflects the
latest user-entered value. This prevents stale values
from being written back to the CVA before the debounce
is flushed.

Adds a regression test covering the debounce scenario.

(cherry picked from commit 2e0cb52dbf)
2026-07-09 12:29:10 -07:00
Matthieu Riegler 1b9964675f fix(forms): allow multiple async validators
When a parent form element defines an async validator, its resource's `params` function needs to evaluate `syncValid()`, which causes unvisited child form nodes to be lazily instantiated. If any of these lazily instantiated child nodes also define an async validator, their resource is initialized while the parent's `params` function is still evaluating. This incorrectly triggers Angular core's `NG0992` guard (`Cannot create a resource inside the params of another resource`).
This commit exports `ɵsetInParamsFunction` and `ɵisInParamsFunction` from `@angular/core` and uses them in `FieldMetadataState.runMetadataCreateLifecycle` to explicitly detach the lazy creation of form metadata from the parent's reactive `params` context.

fixes #69620

(cherry picked from commit 5cb8c733a3)
2026-07-09 09:55:33 -07:00
volkanfilazi 171669f7b2 fix(forms/signals): make extractValue reactive for compat AbstractControl values
(cherry picked from commit b542302a23)
2026-07-08 11:23:05 -07:00
Matthieu Riegler 11836a670a fix(forms): delay mcp reading the form model by a tick
Reading the form model on init is unsafe as it could depend on inputs (eg a required input). We need to delay the read by a tick (after the inputs are set) to ensure that values can be safely read.

fixes #69262

(cherry picked from commit 9604ecfd8b)
2026-06-10 17:14:45 +00:00
arturovt e51ad374ea fix(forms): remove animationstart listener on component destroy to prevent memory leak
The `watchValidity` method in `AnimationInputValidityMonitor` was registering
an anonymous arrow function via `addEventListener` with no corresponding
`removeEventListener` call.

In V8, each closure is represented as a `JSFunction` holding a strong pointer
to a heap-allocated `Context` object containing captured variables
(`VariableLocation::CONTEXT` slots, decided at parse time by
`Scope::MustAllocateInContext`). In Blink, DOM event listeners are stored in
the element's `EventTargetData::event_listener_map` as `JSEventListener`
wrappers backed by a `v8::Persistent<JSFunction>` handle — a strong cross-heap
reference that keeps the function alive as long as the element is alive.

Because the callback passed to `watchValidity` closes over the calling
component/directive (which itself holds a reference back to the element), this
produced a cross-heap reference cycle:

```
  HTMLInputElement (Blink/Oilpan)
    └── EventTargetData → JSEventListener → v8::Persistent<JSFunction>
          └── Context → callback closure
                └── component → HTMLInputElement  ← cycle
```

Neither V8's nor Blink's GC could independently break this cycle because it
crosses the V8/Oilpan heap boundary. The element was therefore never collected
after being removed from the DOM.

The fix stores the listener in a named local variable and registers its removal
via `DestroyRef.onDestroy`, tying cleanup to the lifetime of the component that
owns the element. This ensures `removeEventListener` is called with the exact
same `JSFunction` reference, causing Blink to drop the `v8::Persistent` handle
and allowing both the function and the element to become GC-eligible.

(cherry picked from commit 6cc54e5ede)
2026-06-10 16:56:20 +00:00
Doug Parker 55b7b5a6b6 fix(forms): set additionalProperties: false on generated WebMCP form
This tells the agent that all input properties have been explicitly declared and that it should not attempt to specify additional arguments with unknown names. This provides a little more safety and gives the AI a little more information about the allowed set of inputs for this tool.

(cherry picked from commit df77e42327)
2026-06-04 21:18:10 +00:00
cexbrayat e81c7e8466 refactor(forms): type built-in getError results
Add overloads for built-in validation error keys so callers get precise error payload types from getError.

This enables signal forms template patterns like:

```html
@if (login.getError('minLength'); as minLengthError) {
  <div>Login should be {{ minLengthError.minLength }} characters</div>
}
```

(cherry picked from commit 45e8fb5d6c)
2026-05-29 14:58:47 +02:00
Douglas Parker da82f24330 refactor(forms): add provideExperimentalWebMcpForms
This enables the use of the `experimentalWebMcpTool` option on signal forms and implicitly declares a WebMCP tool based on the form data model. This is an experiment inspirted by the WebMCP declarative forms API to see if Angular's framework-level knowledge of the form's declarative data model can produce higher quality WebMCP tools than the web standard can on its own with less effort from the developer.

Example:

```typescript
// main.ts

import {bootstrapApplication} from '@angular/platform-browser';
import {provideExperimentalWebMcpForms} from '@angular/forms';
import {MyComp} from './form';

bootstrapApplication(MyComp, {
  providers: [
    // Activate the feature.
    provideExperimentalWebMcpForms(),
  ],
});
```

```typescript
// form.ts

import {Component, signal} from '@angular/core';
import {form} from '@angular/forms';

@Component({ /* ... */ })
export class MyComp {
  private readonly f = form(signal({
    firstName: '',
    lastName: '',
  }), {
    // Implicitly creates a WebMCP tool named `createUser` which accepts a `firstName` and `lastName` as parameters.
    experimentalWebMcpTool: {
      name: 'createUser',
      description: 'Creates a user with the given name.',
    },

    // Invokes the submit action when the agent calls the WebMCP tool.
    submission: {
      action: () => {
        console.log('User clicked submit, or agent called the tool!');
      },
    },
  });

  // ...
}
```
2026-05-15 12:10:55 -07:00
kirjs 0806b2f02b refactor(forms): use overloads and JSDoc for deprecations
This commit removes runtime console warnings and uses TypeScript overloads with JSDoc @deprecated annotations to handle backward compatibility for conditional rules.
2026-05-06 14:10:12 -07:00
kirjs 7d9862f0be refactor(forms): support deprecated signatures for backward compatibility
This commit restores support for passing functions directly to hidden, disabled, and readonly rules, marking them as deprecated.
2026-05-06 14:10:12 -07:00
kirjs df54e6a7b2 refactor(forms): use when consistently for conditional rules and validators
This commit updates the signal forms API to use a consistent 'when' parameter for conditional rules and validators, replacing direct function arguments.
2026-05-06 14:10:12 -07:00
Leon Senft 1f30aacbe5 refactor(forms): bind formatted date string to min/max for minDate/maxDate (#68001)
* Test that `minDate`/`maxDate` binds to `min`/`max` on date and time inputs
* Test that `min`/`max` attribute can be set directly on date and time inputs
* Relax type checker to allow `min`/`max` bindings on date and time inputs

PR Close #68001
2026-05-06 11:59:18 -07:00
Leon Senft 276c917b34 refactor(forms): add validation rules for date constraints (#68001)
- Added `minDate()` and `maxDate()` for validating constraints on `Date` inputs.
- `ReadonlyFieldState.min` and `.max` now return
  `Signal<NonNullable<TValue>`. This ensures that `min` and `max` inputs
  on custom controls can accept a reliable type (matching their value
  type).
- Made the `TWrite` type parameter of `MetadataKey` contravariant to
  properly indicate that it's writable.
- Added `LimitKey` as a convenience type for defining validation limit
  metadata (e.g. `MAX_NUMBER`, `MIN_DATE`).
- Added `LimitSelectionKey` which can be used to bind a `LimitKey` with
  value-specific aggregation logic, to a generic metadata key (e.g. use
  `MAX_NUMBER` to aggregate numbers for `MAX`).

PR Close #68001
2026-05-06 11:59:18 -07:00
Leon Senft 592a12d6c9 refactor(forms): remove string support from min and max validation rules (#68001)
The `min` and `max` validation rules previously handled `string` values
to accommodate numbers bound to text inputs. However, this is no longer
necessary as the control binding itself handles the conversion.

This change removes string support from these rules, simplifying the
types to `number | null`. The validation logic has been updated to use
concrete checks (`value === null || Number.isNaN(value)`) to ensure safe
TypeScript narrowing.

Associated tests have been updated to:
- Remove string-specific validation checks.
- Add coverage for text input bindings.
- Add coverage for empty input handling (standard behavior where empty
  sets model to null and skips validation).

BREAKING CHANGE: `min` and `max` validation rules no longer support
string values. Bound values must be numbers or null.

PR Close #68001
2026-05-06 11:59:18 -07:00
Alex Rickabaugh 849dba6c65 fix(forms): implement custom control reset propagation
Introduce a highly decoupled FVC and CVA custom control reset mechanism, and implement the framework-wide automatic `transformedValue` and native controls clearing bridge for both new Signal Forms and legacy forms (Template-driven and Reactive).

1. Custom Control Reset Propagation (Bug #2):
- Establish agnostic custom control resetting via `FormFieldBindingOptions.reset` in `FormField`.
- Ensure that `FieldNode.reset()` unconditionally triggers `writeValue` updates on CVA custom controls.
- Protect against duplicate writes during subsequent change detection updates in `control_cva.ts` by verifying and tracking previous written values in the local bindings cache.

2. Unified Framework-wide FormControl Integration:
- Introduce a monorepo-wide private InjectionToken `ɵFORM_CONTROL_INTEGRATION` and `ɵFormControlIntegration` interface to act as the single, decoupled bridge for hooking up FVC parse errors and receiving control resets across both Signal and legacy forms architectures.
- Simplify Signal Forms: make `FormField` implements `ɵFormControlIntegration` directly, removing the intermediate context object and reducing DI boilerplate down to a clean `useExisting: FormField` provider. Triggers the `onReset` callback directly inside `FormField.reset()`.
- Upgrade Legacy Forms: `NG_CONTROL_INTEGRATION_PROVIDER` provides the renamed token. `NgControl` handles the event subscription internally (`set onReset(callback)`) to recursively listen to `control.events` (`FormResetEvent`) lazily only when assigned, resolving all `FormControl` swapping timing and lifecycle cleanup races automatically.

3. Automatic `transformedValue` and Native Controls Utility Clearing:
- Make `Parser.reset()` method required in the interface for a cleaner and non-defensive execution.
- Wire `transformedValue` into the new integration token `ɵFORM_CONTROL_INTEGRATION` to clear validation parsing states on resets.
- Lazily resets the UI-facing `rawValue` linked signal utilizing the original native `linkedSignal.set` callback (`originalSet`), correctly bypassing the UI-to-model parser loopback and preventing redundant model writes during `reset()`.
- Wire up Native Controls (`control_native.ts\Device`): Hook `parent.onReset` inside native element creation to automatically trigger the native `parser.reset()` and force DOM writes (`setNativeControlValue`) back down to the DOM input value during resets, ensuring native elements with pending parsing validation errors are successfully cleared and synced on form resets.

TAG=agy
CONV=8b4cee1e-2117-42a4-b242-c8ec7bf01752
2026-05-06 10:45:40 -07:00
Suraj Yadav 68c3abbe09 fix(forms): synchronize controls with the model on reset
Synchronize `controlValue` with the model `value` following `reset()`. This
ensures the UI will reflect the form model in cases where a control had a
pending change–delayed by debouncing–at the time it was reset.
2026-05-06 10:45:40 -07:00
Matthieu Riegler 3524de29f3 fix(forms): Add support for range type with outside of native bounds
range inputs don't allow value that are outside their min/max ranges.

fixes #68480
2026-05-05 16:26:03 -07:00
Alex Rickabaugh 5835a5e3a7 fix(forms): prevent orphan field crashes in debounceSync and async validation
- Short-circuit `FieldNode.debounceSync()` if the node is orphaned right
  before calling `this.sync()`, preventing unhandled promise rejections
  on dead state reads.
- Include `this.node.structure.isOrphaned()` in `shouldSkipValidation`
  computed signal in `ValidationState`. This safely shields the entire
  validation layer (sync and async errors) from executing on dead nodes during
  in-flight async validator resolutions.
- Append robust reproduction specs to `orphan_repro.spec.ts` for both the
  `debounceSync` and `validateAsync` async race conditions. Include an intentional
  promise resolution workaround for an experimental Angular `core/resource`
  `PendingTasks` leak deadlock bug uncovered during testing.

TAG=agy
CONV=054e0185-f5f0-40e3-9c9b-413309f36cf6
2026-05-05 15:54:42 -07:00
Alex Rickabaugh 3c44d7c90b fix(forms): fix orphan field error on blur during array removal
Explain the race condition: when an item is deleted from a model array, its
DOM element is removed during change detection, which fires a `blur` event
synchronously. The `blur` handler tries to mark the field as touched,
navigating up to `keyInParent` which throws because the item is already gone
from the array in signals state.

Fix by introducing an `isOrphaned` check that short-circuits `markAsTouched`
early, backed by a reactivity-insulated `childrenMap` poll to avoid double
scans and prevent unhandled exceptions.

TAG=agy
CONV=054e0185-f5f0-40e3-9c9b-413309f36cf6

Fixes #66711

Co-Authored-By: Matthieu Riegler <kyro38@gmail.com>
2026-05-05 15:54:42 -07:00
Leon Senft 708631f2c4 fix(forms): prohibit concurrent submits in signal forms
Prohibit concurrent submits in signal forms to prevent duplicate actions and side effects when a submission is already in progress.

If `submit()` is called while a prior submit is in progress for the same field or any of its parents, it returns `false` immediately without running the action again.

This commit also updates the documentation in `form-submission.md` to reflect this behavior.

Fixes #68317
2026-05-05 11:14:03 -07:00
Alex Rickabaugh 9b9769479b perf(forms): shortcut deepSignal writes if value is unchanged
Avoid deep write path traversal and triggering source signal updates
when calling `deepSignal.set(value)` with a new value that is
identical to the current value (`Object.is`).

This shortcuts the entire write path and unnecessary array/object
copying early on. This approach relies on the guarantee that `source`'s
value is non-nullable in the context where `deepSignal` is created and
used.

TAG=agy
CONV=9e5bd277-0d0a-466c-be36-5e3a8e6910be
2026-05-05 09:28:00 -07:00
Alex Rickabaugh 0ea50ffe5a fix(forms): ensure debounced async validators produce pending status during debounce
When using a debounced async validator, the pending status from the internal
debounced resource was not flowing through to the resource created by the
factory. Replicate the 'chain' logic using the new privately exported ɵchain
function to propagate the loading status correctly.

Fixes #68105
2026-05-04 13:03:07 -07:00
Alex Rickabaugh e0536091f5 perf(forms): optimize reactivity by using shallow array equality
Add `shallowArrayEquals` to computed signals returning arrays of errors or reasons in Signal Forms. This prevents unnecessary downstream invalidations when the content of the arrays remains unchanged.
2026-04-30 15:41:45 -07:00
Matthieu Riegler b2083a7fd2 build: cleanup workspace deps
Some of the deps are move down to the only targets that uses them.
2026-04-23 11:38:26 -07:00
Alex Rickabaugh f9f24fc669 feat(forms): shim legacy NG_VALIDATORS into parseErrors for CVA mode (#67943)
- Injected `NG_VALIDATORS` into `FormField` and exposed it via an internal getter.
- Created a `computed` signal in `cvaControlCreate` to run legacy validators and map into standard validation errors without generic `as any` type assertions.
- Intercepted `registerOnValidatorChange` to trigger updates even when the model value remains unchanged (e.g., going from `null` to `null`).
- Added integration tests to verify parse error propagation and reactivity.

PR Close #67943
2026-04-14 18:32:24 +03:00
Alex Rickabaugh 72d3ace03c fix(forms): use controlValue in NgControl for CVA interop (#67943)
use controlValue() instead of value() to ensure that CVA controls see the most recent user input immediately rather than waiting for it to be synchronized after debouncing

PR Close #67943
2026-04-14 18:32:24 +03:00
Alex Rickabaugh de56d74da3 fix(forms): align FormField CVA selection priority with standard forms
Prioritize custom ControlValueAccessor instances over default or built-in accessors when applying the [formField] directive. This is achieved by directly consuming selectValueAccessor from @angular/forms, ensuring absolute alignment with the precedence rules used across standard Angular form directives.
2026-04-07 14:21:44 -07:00
Alex Rickabaugh 2e9aeea0fe fix(forms): deduplicate writeValue calls in CVA interop
Update bindings['controlValue'] during onChange to track the view value. This allows bindingUpdated to skip writeValue if the model value matches the last seen view value, preventing redundant writes.

Fixes #67847
2026-04-07 14:21:44 -07:00
Alex Rickabaugh 394ad0c2a2 fix(forms): allow late-bound input types for signals forms
Ensure that input [type] bindings are evaluated dynamically rather than cached eagerly during initialization. This allows late-bound expressions for input types to correctly apply constraints like min/max and maxLength.

Fixes #66987
2026-04-03 10:18:09 -07:00
Alex Rickabaugh 24e52d450d feat(forms): add debounce option to validateAsync and validateHttp
This adds support for a `debounce` option to the `validateAsync` and `validateHttp` functions.
This allows developers to debounce the triggering of async validators to improve performance.

A `DebounceTimer` type was also added to `@angular/core` to represent the wait condition parameters uniformly.
2026-03-25 14:17:52 -07:00
Leon Senft b5af15a332 test(forms): support directive composition with FormField
Support composing the `FormField` directive with custom controls:
2026-03-25 13:35:02 -07:00
Alex Rickabaugh 16adbbf423 fix(core): ensure custom controls resolve transitive host directives
Custom controls can be modeled using a set of host directives to alias
and expose value and valueChange (or checked/checkedChange) bindings,
as well as native attributes like disabled.

This commit updates initializeCustomControlStatus to correctly identify
host components using mapped inputs/outputs, even when those inputs are
exposed via transitive host directives. It also updates
customControlHasInput so that the custom control presence check correctly
evaluates the exposed inputs across all applied host directives, caching
the result to optimize performance on hot code paths.
2026-03-25 13:00:46 -07:00
Matthieu Riegler eae8f7e30b feat(core): Set default Component changeDetection strategy to OnPush
The default change detection strategy is now OnPush.

BREAKING CHANGE: Component with undefined `changeDetection` property are now `OnPush` by default. Specify `changeDetection: ChangeDetectionStrategy.Eager` to keep the previous behavior.
2026-03-24 16:25:02 -07:00
Alex Rickabaugh 709f5a390c feat(forms): add FieldState.getError()
Added a `getError(kind: string)` method to `FieldState` that returns the first validation error of a given kind, or `undefined` if no such error exists. This method is reactive and will re-evaluate when errors change.

Fixes #63905

Also updated public API goldens and added unit tests.
2026-03-24 15:07:55 -07:00
Alex Rickabaugh ee8d2098cb fix(forms): change FieldState optional properties to non-optional | undefined
This improves compatibility with TypeScript's exactOptionalPropertyTypes.

Fixes #67246
2026-03-24 14:51:31 -07:00
Leon Senft 0eeb1b5f03 fix(forms): allow FormRoot to be used without submission options (#67727)
The `[formRoot]` directive will no longer call `submit()` if the bound
form doesn't define its own submission options. This allows the
directive to be used solely for the default behavior it provides:
setting `novalidate` on the `<form>` and calling `preventDefault()` on
the `submit` event.

Fix #67367

PR Close #67727
2026-03-23 15:41:19 -07:00
Leon Senft f4a5b42ebe refactor(forms): rename directive files for consistency (#67727)
- **FormField**:  `form_field_directive.ts` -> `form_field.ts`
- **FormRoot**:   `ng_signal_form.ts`       -> `form_root.ts`

PR Close #67727
2026-03-23 15:41:19 -07:00
Alex Rickabaugh df8b020299 fix(forms): clear native date inputs correctly in signal forms when changed via native UI
When a native date input gets cleared manually by a user via the internal browser
UI, the element changes from invalid to valid, but no `input` event is emitted.

This commit introduces `InputValidityMonitor`, an injectable service that
intercepts these edge-case native status changes. The monitor dynamically
installs CSP-compliant styles appending specific animation keyframes for
`:valid` and `:invalid` pseudoclasses on native form controls. By attaching an
`animationstart` listener, Angular intercepts these changes immediately and
re-invokes the parser.

Fixes #67300
2026-03-20 15:10:26 -07:00
Alex Rickabaugh 98c5afdb02 perf(forms): lazily instantiate signal form fields
Currently, Signal Forms eagerly instantiates all nodes in the form tree because `childrenMap` iterates over the `value` and creates a `FieldNode` for every property. This ensures validation side-effects are run early, but creates pure overhead for fields without validation logic unless explicitly accessed.

This commit makes `childrenMap` lazy by default, skipping materialization for children without schema logic. This is achieved by introducing `hasLogicRules()` and `anyChildHasLogic()` across the `LogicNode` hierarchy. Fields are now only instantiated when a direct read occurs via `getChild()` (which calls the new `ensureChildrenMap()`) or if their subtree requires eager evaluation due to existing validation rules.

Fixes #67212
2026-03-20 15:09:26 -07:00
Alex Rickabaugh 50e599e73e fix(core): lazy-initialize debounced state to prevent computation cycle
When building a debounced resource, we previously eagerly started tracking the 'source' signal state by instantiating a regular signal. However, if this 'debounced' primitive is initialized in a computation reactive graph (like signal forms 'validateAsync'), reading the current UI source dependency eagerly can induce a cycle if we haven't finished calculating the graph node yet.

This fix uses a 'linkedSignal' block to define the eager 'source' instead. Because linkedSignals are lazy by default, this bypasses the initial eager evaluation, allowing the containing reactive graph to finish forming first without losing our timing logic inside the ambient effect().
2026-03-19 16:18:38 -07:00
Alex Rickabaugh 41b1410cb8 feat(forms): support binding number|null to <input type="text">
`<input type="number">` often does not provide the desired user experience when editing numbers in
a form. MDN even [describes](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number#using_number_inputs)
how text inputs should be used in many cases instead, via `<input type="text" inputmode="numeric">`
or similar configurations. Previously, this did not work with Signal Forms without a custom input
component/directive.

This PR builds support for binding `number|null` models directly to `<input type="text">` native
controls via `[formField]`. When a model has a number or `null` value, signal forms will preserve
that status when the user makes edits/changes. Empty string values are converted to `null`, other
values are parsed as numbers, and a parse error is raised when a non-numeric value is entered.

Note that it's up to the UI developer to configure additional UI affordances such as setting an
appropriate `inputmode`, rejecting non-numeric keypresses, etc.

Fixes #66903
Fixes #66157
2026-03-19 15:26:34 -07:00
Alex Rickabaugh 74f76d8075 feat(forms): add reloadValidation to Signal Forms to manually trigger async validation
This commit introduces a formal mechanism to manually re-trigger
asynchronous validations in Signal Forms, addressing #66994.

It exposes a `reloadValidation` method on the `FieldState` interface
that recursively cascades down the form tree and invokes the underlying
`ResourceRef`'s `reload()` method for any metadata keys tagged with the
internal `IS_ASYNC_VALIDATION_RESOURCE` symbol.

Fixes #66994
2026-03-19 15:22:13 -07:00