Adds a test verifying that `transformedValue` exposes parse errors via
the returned signal's `parseErrors()` property when no FormField
context is present.
This ensures that:
- parse errors are still observable without DI-based field propagation
- the model is not updated when `parse` omits `value`
- valid input clears parse errors and updates the model
This test protects the documented contract that DI-based error
propagation is expected for FormValueControl usage, while standalone
usage relies on explicit consumption of `parseErrors()`.
(cherry picked from commit 71b8159b37)
Expands the `debounce` rule configuration to accept `'blur'`. When this option
is provided, the rule will delay model synchronization until the field loses
focus (is touched). This introduces a debouncer that defers resolution
until the framework automatically aborts pending debounces upon touch events.
(cherry picked from commit c767d678cf)
Aligns the errors returned from the `parse` function in
`transformedValue` to use the same convention as the rest of signal
forms (a property called `error` that can contain a single error or list
of errors)
(cherry picked from commit 23fd8fa586)
Changes `parsedErrors` to a `linkedSignal` based on the model value.
This ensures that the parse errors are reset if the model changes from
outside the control.
PR Close#66917
The `action` and `onInvalid` handlers now recevie two pieces of
information:
1. The form that is being submitted
2. The specific field that the submit was triggered on
Remove the `submit()` method on field state - supporting this is complex
from a typing perspective, since the `FieldState` only knows its
`TValue` type, not the `TModel` type of its owning `FieldTree`. Rather
than try to pack additional generics on to `FieldState`, we'll just
leave the `submit` function as a standalone importable function.
The `fieldTree` property of `FieldState` returns its associated
`FieldTree`.
Note that the round trip from `FieldTree` to `FieldState` and back will
lose type information. This is because `FieldState` intentionally does
not know whether it came from a pure Signal Forms field tree, or a
Reactive Forms compatible field tree:
```ts
// Pure Signal Forms:
const x: FieldTree<string>;
x(); // FieldState<string>;
x().fieldTree; // FieldTree<unknown>
// Reactive Forms compatibility:
const y: FieldTree<FormControl<string>>;
y(); // FieldState<string>;
y().fieldTree; // FieldTree<unknown>;
```
Reoves the `parseErrors` property on `FormUiControl` and instead
introduces a new utility `transformedValue` that automatically handles
synchronizing the raw value and model value using the given `parse` and
`format` functions. It also automates the reporting of `parseErrors` to
the `FormField`, simplifying the API surface
Allow `validateStandardSchema()` to consume a computed schema so
validation rules stay in sync when the schema changes over time.
This supports schemas stored in computed signals (e.g. zod schemas that
depend on input signals) and ensures the effective schema updates after
initialization instead of being captured once.
Fixes#66867
Updates FormOptions to accept a submission configuration object.
This allows defining default submit options (action, validation behavior, etc.)
when creating the form, which can be overridden when calling submit().
Changes the `submit` function signature to accept a `FormSubmitOptions` object instead of a direct action callback.
This allows for more flexibility, including:
- `action`: The standard submit action to perform with the data.
- `onInvalid`: A callback to execute when the submit action is not triggered due to failing validation
- `ignoreValidators`: Controls whether pending validators or invalid validators should be ignored
Also updates the return value of `submit` to a `Promise<boolean` to indicate submission success.
Remove `setControlValue()` from `FieldState` and convert `controlValue` to a
`WritableSignal` whose setter implements the debounced syncing behavior
of `setControlValue()`.
Don't touch hidden, disabled, or readonly fields on submit, since they
don't contribute to form validity. This also prevents errors from
appearing immediately if they're later made interactive.
Fix#66344
Ensure `submit()` behaves as expected while a form is pending.
- Submission is not blocked by pending validation.
- Submission errors prevent pending validation errors from appearing
after they resolve on the same field.
- Submission errors don't prevent pending validation errors from
appearing after they resolve on subfields.
Parse errors allow a custom control to communicate that it is currently
unable to produce a valid value.
Parse errors are reported by implementing the optional `parseErrors`
property on the `FormUiControl`. The property should be a signal of the
current parse errors.
Also renames several `*Field` types to `*FieldTree`. This aligns with the new naming of the concept after `Field` was renamed
to `FieldTree`.
Previously we were unconditionally setting the control value back into
the value, regardless of if it had actually been changed. This PR
changes the logic to flush the pending sync on touch if there is one, or
just skip it if there isn't.
Add comprehensive test cases to validate behavior when multiple
pattern validators are applied to the same field.
These tests clarify that multiple patterns operate with AND logic,
where each pattern is validated independently and produces its own
error when it doesn't match.
This completes the rename started in #66136. `[field]` is too generic of
a selector for the forms system to own, and likely to cause naming
collisions with existing components. Therefore it is being renamed to
`[formField]`
Remove the `customError` function and `CustomValidationError` type.
These were made obsolete by support for returning plain object literals
as custom errors.
This also catches few `field` properties that were missed in the
renaming to `fieldTree`.
Removes the abort event listener once the debounce timeout completes.
This avoids lingering listeners, prevents potential memory leaks, and ensures
the abort logic runs at most once.
This PR makes a number of changes to the metadata API to address design
flaws in the previous API. Some of the changes include:
- Replaces the previous `MetadataKey` and `AggregateMetadataKey` with a
single unified `MetadataKey` that is used for all metadata.
- The new `MetadataKey` is only defined for fields that explicitly set
it in their schema logic
- All metadata now has reducer / aggregate behavior
- The new `MetadataKey` has an option to create a managed key which
wraps the result of its computed aggregate into some other structure
such as a `Resource` or `linkedSignal`
- There are now two APIs to create metadata keys
- `createMetadataKey` for pure computed metadata
- `createManagedMetadataKey` for metadata that manages its computation
internally
Previously, navigating a `FieldTree` in signal forms involved reactive reads
of the value of the parent field(s), both directly and via `.childrenMap()`.
This meant that on _any_ change to the value of a field, reactive
notifications would trigger updates of computeds, reruns of effects, etc.
So for example, this effect would run on every change to the form:
```ts
const f = form(signal({data: 'abc', unrelated: 0}));
effect(() => {
// accessing f.data incurs a dependency on f().value() which changes
// on every change in the whole form
console.log(f.data().value());
});
```
This is deeply counterintuitive and troublesome when attempting to write
effect logic, and also results in `computed`s unnecessarily updating.
This change introduces the concept of a "reader" computed, which memoizes
the access of a field at a given key via the reactive graph. With this, the
same `f.data` access above now depends on the `data` reader in `f` only,
which is effectively a constant computed. As a result, the effect only
reruns on changes to `data`'s value, as intended.
PR Close#65802
If we're calling `min` on a path that's guaranteed to be `number` we
don't want to make the users validator function handle the `null` or
`string` cases.
This uncovered an issue in the `SchemaTreePath` type which needed to be
fixed by preventing the model type from being distributed over.
PR Close#65212
Relaxes the constraints on which paths can be used with the `min` &
`max` validation rules, since people may want to validate a
potentially-null number, or a numeric value represented as a string
PR Close#65212
Add an `AbortSignal` parameter to `Debouncer`. Implementations may
choose to accept this parameter to be informed when a debounced
operation is aborted. This may be useful for canceling pending timers or
avoiding unnecessary work.
The `debounce()` rule allows developers to control when changes to a
form control are synchronized to the form model.
This feature necessitated some changes to `FieldState`:
* `controlValue` is a new signal property that represents the current
value of a form field as it appears in its corresponding control.
* `value` conceptually remains unchanged; however, its value may lag
behind that of `controlValue` if a `debounce()` rule is applied.
The `debounce()` rule essentially manages when changes to `controlValue` are
synchronized to `value`. The intent is that an expensive or slow
validation rule can react to the debounced `value`, rather than a more
frequently changing `controlValue`.
Directly updating `value` immediately updates `controlValue`, and cancels any
pending debounced updates.
When multiple `debounce()` rules are applied to the same field, the last
currently active rule is used to debounce an update. These rules are
applied to child fields as well, unless they override them with their
own rule.
Currently we maintain the pathKeys internally, but do not expose them
through the `FieldContext`, this PR updates the `FieldContext` to expose
this property.