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
(cherry picked from commit ebc5c2b083)
FormUiControl states that hidden, pending and dirty will be bind in custom controls, but this is currently not the case.
Fixes#65575
(cherry picked from commit 9fe9566581)
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
Extends the `FormUiControl` interface to allow `InputSignalWithTransform` in addition to `InputSignal` for its properties.
Fixes#65756
(cherry picked from commit 7d1e502345)
This is necessary to exclude a race condition where the MutationObserver initialized by the instruction fired before the inputs are binded.
fixes#65678
(cherry picked from commit f35b2ef47c)
Adds a DI configuration option for signal forms that allows the
developer to specify CSS classes that should be automatically added
by the `Field` directive based on the field's status.
(cherry picked from commit c70e246c23)
The type checker will no longer prohibit binding the Signal Forms `[field]`
directive to an input with a dynamic `[attr.type]` or `[type]` binding.
(cherry picked from commit 3a1eb07c46)
Support binding `[field]` to directives that implement
`FormValueControl` or `FormCheckboxControl`.
The `[field]` binds to whichever directive (or component) matches first in the
event there are multiple implementations. We are considering whether to make
this an error state, which could be reported during type checking.
Closes#63910, Closes#64992
(cherry picked from commit f97a1d4856)
An early piece of feedback received regarding custom controls hosted on
native inputs was that they required a lot of boilerplate to bind
`FieldState` properties. Each property required an input to accept the
property, and a host binding to forward it to the native control.
(cherry picked from commit c727df5d38)
* Apply any debounce rules to updates from interop controls (if configured).
* Add tests to ensure debouncing works for all control types (native, custom,
and interop).
(cherry picked from commit b1037ec2f0)
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
Test that a component with a bound `[field]` input is not treated as a
control, and that `fieldBinding` does not include the corresponding
`Field` instance.
(cherry picked from commit acb78eeb7a)
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.
(cherry picked from commit 98ce9a7b17)
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.
(cherry picked from commit d337cfb68f)
Fixes some type checking errors in the signal forms tests that accumulated while there wasn't any type checking.
(cherry picked from commit 10915f2f5f)
Currently we maintain the pathKeys internally, but do not expose them
through the `FieldContext`, this PR updates the `FieldContext` to expose
this property.
(cherry picked from commit 2fd8dc9195)
Improves support for using `Object.keys` (and related methods) and
`Symbol.iterator` to work with a `FieldTree` of unknown shape.
(cherry picked from commit e87f423d90)
When we set the `value` on a `<select>` element we're really just
setting the selected `<option>`. It treats the selected option as the
source of truth, rather than the actual value. This means that if
options are added or removed or their value changes, the `<select>` may
wind up with a different `value` than what's in our model.
This PR resolves this issue by adding a `MutationObserver` to the select
that is used to resync its value to the model whenever the options may
have changed.
(cherry picked from commit 194b41199b)
For each field state property, check if it has changed since the last
time it was checked before writing it the corresponding form control
property.
The `pattern` and `required` properties of the field state now return a
default value rather than `undefined` if not defined by metadata.
(cherry picked from commit 41be02da2f)
In some cases the logic order was not preserved properly when using `apply`. In particular this occurs when some logic is registered on a child of the root, followed by an apply to the root, followed by further logic registered on a child. In this case the final registered logic wound up running before the applied logic.
This happened because `FieldPathNode` for a child path was caching its `LogicNodeBuilder` at creation time. This meant that if the parent's `LogicNodeBuilder` changed (e.g., due to an `apply` call), the child would still be using the old one.
This commit fixes the issue by dynamically resolving the `LogicNodeBuilder` for a child path whenever it is accessed.
(cherry picked from commit fa13a167f1)
This commit changes arrays in a parent array to be tracked the same way
as primitive values like strings and numbers. This is necessary because
the tracking key symbol used to maintain identity for objects in an
array does not survive the array spread operation:
```
return {...oldValue} // tracking symbol preserved ✅
return [...oldValue] // tracking symbol lost ❌
```
(cherry picked from commit f80b51a738)
The directive implemnetation might set CVA values during the template evaluation. Since the template is a reactive context we need to untrack when setting the CVA values to prevent writing to signals in a reactive context.
fixes#64614
PR Close#64618
Renames the field state related to metadata to reflect the new
"metadata" name. In particular:
- `property(...)` is renamed to `metadata(...)`
- `hasProperty(...)` is renamed to `hasMetadata(...)`
PR Close#64603
Renames logic functions related to metadata to align with the new
"metadata" name. Notably:
- `property(...)` => `metadata(...)`
- `aggregateProperty(...)` => `aggregateMetadata(...)`
PR Close#64603
Prior to this change, `FieldState` defined a signal for each built-in
property. This unfortunately meant that the `Field` directive had no way
of knowing which property had actually been defined in the schema, and
would thus attempt to propagate them all to the bound form control. This
meant that the default values of these signals would override the
default or template defined values of these control properties.
Now these properties are `undefined` by default, and only initialized if
defined in the schema. Thus the `Field` directive will not attempt to
bind any properties that aren't explicitly managed by the schema.
PR Close#64446
Note that unlike the other built-in properties, `pattern` is not
propagated to native controls so the lack of symmetry with other
property tests is intentional.
PR Close#64446
Add support for interoperability between signal forms and reactive forms that
commit effccffde0 had removed.
A signal forms field can once again be bound to any element or component with a
`ControlValueAccessor`.
PR Close#64471