@angular/forms 21.0.0-next.5 → 21.0.0-next.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,12 @@
1
1
  /**
2
- * @license Angular v21.0.0-next.5
3
- * (c) 2010-2025 Google LLC. https://angular.io/
2
+ * @license Angular v21.0.0-next.7
3
+ * (c) 2010-2025 Google LLC. https://angular.dev/
4
4
  * License: MIT
5
5
  */
6
6
 
7
7
  import { httpResource } from '@angular/common/http';
8
8
  import * as i0 from '@angular/core';
9
- import { computed, untracked, ɵSIGNAL as _SIGNAL, inject, Injector, Renderer2, signal, ElementRef, effect, afterNextRender, DestroyRef, Directive, Input, reflectComponentType, OutputEmitterRef, EventEmitter, runInInjectionContext, linkedSignal, APP_ID, ɵisPromise as _isPromise, resource } from '@angular/core';
10
- import { Validators, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
9
+ import { computed, untracked, InjectionToken, inject, Injector, input, ɵCONTROL as _CONTROL, effect, Directive, runInInjectionContext, linkedSignal, signal, APP_ID, ɵisPromise as _isPromise, resource } from '@angular/core';
11
10
  import { SIGNAL } from '@angular/core/primitives/signals';
12
11
 
13
12
  /**
@@ -1426,212 +1425,37 @@ function validateHttp(path, opts) {
1426
1425
  }
1427
1426
 
1428
1427
  /**
1429
- * A fake version of `NgControl` provided by the `Control` directive. This allows interoperability
1430
- * with a wider range of components designed to work with reactive forms, in particular ones that
1431
- * inject the `NgControl`. The interop control does not implement *all* properties and methods of
1432
- * the real `NgControl`, but does implement some of the most commonly used ones that have a clear
1433
- * equivalent in signal forms.
1428
+ * Lightweight DI token provided by the {@link Control} directive.
1434
1429
  */
1435
- class InteropNgControl {
1436
- field;
1437
- constructor(field) {
1438
- this.field = field;
1439
- }
1440
- control = this;
1441
- get value() {
1442
- return this.field().value();
1443
- }
1444
- get valid() {
1445
- return this.field().valid();
1446
- }
1447
- get invalid() {
1448
- return this.field().invalid();
1449
- }
1450
- get pending() {
1451
- return this.field().pending();
1452
- }
1453
- get disabled() {
1454
- return this.field().disabled();
1455
- }
1456
- get enabled() {
1457
- return !this.field().disabled();
1458
- }
1459
- get errors() {
1460
- const errors = this.field().errors();
1461
- if (errors.length === 0) {
1462
- return null;
1463
- }
1464
- const errObj = {};
1465
- for (const error of errors) {
1466
- errObj[error.kind] = error;
1467
- }
1468
- return errObj;
1469
- }
1470
- get pristine() {
1471
- return !this.field().dirty();
1472
- }
1473
- get dirty() {
1474
- return this.field().dirty();
1475
- }
1476
- get touched() {
1477
- return this.field().touched();
1478
- }
1479
- get untouched() {
1480
- return !this.field().touched();
1481
- }
1482
- get status() {
1483
- if (this.field().disabled()) {
1484
- return 'DISABLED';
1485
- }
1486
- if (this.field().valid()) {
1487
- return 'VALID';
1488
- }
1489
- if (this.field().invalid()) {
1490
- return 'INVALID';
1491
- }
1492
- if (this.field().pending()) {
1493
- return 'PENDING';
1494
- }
1495
- throw Error('AssertionError: unknown form control status');
1496
- }
1497
- valueAccessor = null;
1498
- hasValidator(validator) {
1499
- // This addresses a common case where users look for the presence of `Validators.required` to
1500
- // determine whether or not to show a required "*" indicator in the UI.
1501
- if (validator === Validators.required) {
1502
- return this.field().property(REQUIRED)();
1503
- }
1504
- return false;
1505
- }
1506
- updateValueAndValidity() {
1507
- // No-op since value and validity are always up to date in signal forms.
1508
- // We offer this method so that reactive forms code attempting to call it doesn't error.
1509
- }
1510
- }
1511
-
1512
- // TODO: These utilities to be replaced with proper integration into framework.
1513
- function privateGetComponentInstance(injector) {
1514
- assertIsNodeInjector(injector);
1515
- if (injector._tNode.directiveStart === 0 || injector._tNode.componentOffset === -1) {
1516
- return undefined;
1517
- }
1518
- return injector._lView[injector._tNode.directiveStart + injector._tNode.componentOffset];
1519
- }
1520
- function privateSetComponentInput(inputSignal, value) {
1521
- inputSignal[_SIGNAL].applyValueToInputSignal(inputSignal[_SIGNAL], value);
1522
- }
1523
- function privateIsSignalInput(value) {
1524
- return isInputSignal(value);
1525
- }
1526
- function privateIsModelInput(value) {
1527
- return isInputSignal(value) && isObject(value) && 'subscribe' in value;
1528
- }
1529
- function privateRunEffect(ref) {
1530
- ref[_SIGNAL].run();
1531
- }
1532
- function assertIsNodeInjector(injector) {
1533
- if (!('_tNode' in injector)) {
1534
- throw new Error('Expected a Node Injector');
1535
- }
1536
- }
1537
- function isInputSignal(value) {
1538
- if (!isObject(value) || !(_SIGNAL in value)) {
1539
- return false;
1540
- }
1541
- const node = value[_SIGNAL];
1542
- return isObject(node) && 'applyValueToInputSignal' in node;
1543
- }
1544
-
1430
+ const CONTROL = new InjectionToken(typeof ngDevMode !== undefined && ngDevMode ? 'CONTROL' : '');
1545
1431
  /**
1546
- * Binds a form `Field` to a UI control that edits it. A UI control can be one of several things:
1432
+ * Binds a form `FieldTree` to a UI control that edits it. A UI control can be one of several things:
1547
1433
  * 1. A native HTML input or textarea
1548
1434
  * 2. A signal forms custom control that implements `FormValueControl` or `FormCheckboxControl`
1549
- * 3. A component that provides a ControlValueAccessor. This should only be used to backwards
1435
+ * 3. TODO: https://github.com/orgs/angular/projects/60/views/1?pane=issue&itemId=131712274. A
1436
+ * component that provides a ControlValueAccessor. This should only be used to backwards
1550
1437
  * compatibility with reactive forms. Prefer options (1) and (2).
1551
1438
  *
1552
1439
  * This directive has several responsibilities:
1553
1440
  * 1. Two-way binds the field's value with the UI control's value
1554
1441
  * 2. Binds additional forms related state on the field to the UI control (disabled, required, etc.)
1555
1442
  * 3. Relays relevant events on the control to the field (e.g. marks field touched on blur)
1556
- * 4. Provides a fake `NgControl` that implements a subset of the features available on the reactive
1557
- * forms `NgControl`. This is provided to improve interoperability with controls designed to work
1558
- * with reactive forms. It should not be used by controls written for signal forms.
1443
+ * 4. TODO: https://github.com/orgs/angular/projects/60/views/1?pane=issue&itemId=131712274.
1444
+ * Provides a fake `NgControl` that implements a subset of the features available on the
1445
+ * reactive forms `NgControl`. This is provided to improve interoperability with controls
1446
+ * designed to work with reactive forms. It should not be used by controls written for signal
1447
+ * forms.
1559
1448
  *
1560
1449
  * @category control
1561
1450
  * @experimental 21.0.0
1562
1451
  */
1563
1452
  class Control {
1564
- /** The injector for this component. */
1565
1453
  injector = inject(Injector);
1566
- renderer = inject(Renderer2);
1567
- /** Whether state synchronization with the field has been setup yet. */
1568
- initialized = false;
1569
- /** The field that is bound to this control. */
1570
- field = signal(undefined, ...(ngDevMode ? [{ debugName: "field" }] : []));
1571
- // If `[control]` is applied to a custom UI control, it wants to synchronize state in the field w/
1572
- // the inputs of that custom control. This is difficult to do in user-land. We use `effect`, but
1573
- // effects don't run before the lifecycle hooks of the component. This is usually okay, but has
1574
- // one significant issue: the UI control's required inputs won't be set in time for those
1575
- // lifecycle hooks to run.
1576
- //
1577
- // Eventually we can build custom functionality for the `Control` directive into the framework,
1578
- // but for now we work around this limitation with a hack. We use an `@Input` instead of a
1579
- // signal-based `input()` for the `[control]` to hook the exact moment inputs are being set,
1580
- // before the important lifecycle hooks of the UI control. We can then initialize all our effects
1581
- // and force them to run immediately, ensuring all required inputs have values.
1582
- set _field(value) {
1583
- this.field.set(value);
1584
- if (!this.initialized) {
1585
- this.initialize();
1586
- }
1587
- }
1588
- /** The field state of the bound field. */
1454
+ field = input.required(...(ngDevMode ? [{ debugName: "field", alias: 'control' }] : [{ alias: 'control' }]));
1589
1455
  state = computed(() => this.field()(), ...(ngDevMode ? [{ debugName: "state" }] : []));
1590
- /** The HTMLElement this directive is attached to. */
1591
- el = inject(ElementRef);
1592
- /** The NG_VALUE_ACCESSOR array for the host component. */
1593
- cvaArray = inject(NG_VALUE_ACCESSOR, { optional: true });
1594
- /** The Cached value for the lazily created interop NgControl. */
1595
- _ngControl;
1596
- /** A fake NgControl provided for better interop with reactive forms. */
1597
- get ngControl() {
1598
- return (this._ngControl ??= new InteropNgControl(() => this.state()));
1599
- }
1600
- /** The ControlValueAccessor for the host component. */
1601
- get cva() {
1602
- return this.cvaArray?.[0] ?? this._ngControl?.valueAccessor ?? undefined;
1603
- }
1604
- /** Initializes state synchronization between the field and the host UI control. */
1605
- initialize() {
1606
- this.initialized = true;
1607
- const injector = this.injector;
1608
- const cmp = privateGetComponentInstance(injector);
1609
- // If component has a `control` input, we assume that it will handle binding the field to the
1610
- // appropriate native/custom control in its template, so we do not attempt to bind any inputs on
1611
- // this component.
1612
- if (cmp && isShadowedControlComponent(cmp)) {
1613
- return;
1614
- }
1615
- if (cmp && isFormUiControl(cmp)) {
1616
- // If we're binding to a component that follows the standard form ui control contract,
1617
- // set up state synchronization based on the contract.
1618
- this.setupCustomUiControl(cmp);
1619
- }
1620
- else if (this.cva !== undefined) {
1621
- // If we're binding to a component that doesn't follow the standard contract, but provides a
1622
- // control value accessor, set up state synchronization based on th CVA.
1623
- this.setupControlValueAccessor(this.cva);
1624
- }
1625
- else if (this.el.nativeElement instanceof HTMLInputElement ||
1626
- this.el.nativeElement instanceof HTMLTextAreaElement ||
1627
- this.el.nativeElement instanceof HTMLSelectElement) {
1628
- // If we're binding to a native html input, set up state synchronization with its native
1629
- // properties / attributes.
1630
- this.setupNativeInput(this.el.nativeElement);
1631
- }
1632
- else {
1633
- throw new Error(`Unhandled control?`);
1634
- }
1456
+ [_CONTROL] = undefined;
1457
+ // TODO: https://github.com/orgs/angular/projects/60/views/1?pane=issue&itemId=131861631
1458
+ register() {
1635
1459
  // Register this control on the field it is currently bound to. We do this at the end of
1636
1460
  // initialization so that it only runs if we are actually syncing with this control
1637
1461
  // (as opposed to just passing the field through to its `control` input).
@@ -1643,308 +1467,13 @@ class Control {
1643
1467
  });
1644
1468
  }, { injector: this.injector });
1645
1469
  }
1646
- /**
1647
- * Set up state synchronization between the field and a native <input>, <textarea>, or <select>.
1648
- */
1649
- setupNativeInput(input) {
1650
- const inputType = input instanceof HTMLTextAreaElement
1651
- ? 'text'
1652
- : input instanceof HTMLSelectElement
1653
- ? 'select'
1654
- : input.type;
1655
- input.addEventListener('input', () => {
1656
- switch (inputType) {
1657
- case 'checkbox':
1658
- this.state().value.set(input.checked);
1659
- break;
1660
- case 'radio':
1661
- // The `input` event only fires when a radio button becomes selected, so write its `value`
1662
- // into the state.
1663
- this.state().value.set(input.value);
1664
- break;
1665
- case 'number':
1666
- case 'range':
1667
- case 'datetime-local':
1668
- // We can read a `number` or a `string` from this input type.
1669
- // Prefer whichever is consistent with the current type.
1670
- if (typeof this.state().value() === 'number') {
1671
- this.state().value.set(input.valueAsNumber);
1672
- }
1673
- else {
1674
- this.state().value.set(input.value);
1675
- }
1676
- break;
1677
- case 'date':
1678
- case 'month':
1679
- case 'week':
1680
- case 'time':
1681
- // We can read a `Date | null` or a `number` or a `string` from this input type.
1682
- // Prefer whichever is consistent with the current type.
1683
- if (isDateOrNull(this.state().value())) {
1684
- this.state().value.set(input.valueAsDate);
1685
- }
1686
- else if (typeof this.state().value() === 'number') {
1687
- this.state().value.set(input.valueAsNumber);
1688
- }
1689
- else {
1690
- this.state().value.set(input.value);
1691
- }
1692
- break;
1693
- default:
1694
- this.state().value.set(input.value);
1695
- break;
1696
- }
1697
- this.state().markAsDirty();
1698
- });
1699
- input.addEventListener('blur', () => this.state().markAsTouched());
1700
- this.maybeSynchronize(() => this.state().readonly(), this.withBooleanAttribute(input, 'readonly'));
1701
- // TODO: consider making a global configuration option for using aria-disabled instead.
1702
- this.maybeSynchronize(() => this.state().disabled(), this.withBooleanAttribute(input, 'disabled'));
1703
- this.maybeSynchronize(() => this.state().name(), this.withAttribute(input, 'name'));
1704
- this.maybeSynchronize(this.propertySource(REQUIRED), this.withBooleanAttribute(input, 'required'));
1705
- this.maybeSynchronize(this.propertySource(MIN), this.withAttribute(input, 'min'));
1706
- this.maybeSynchronize(this.propertySource(MIN_LENGTH), this.withAttribute(input, 'minLength'));
1707
- this.maybeSynchronize(this.propertySource(MAX), this.withAttribute(input, 'max'));
1708
- this.maybeSynchronize(this.propertySource(MAX_LENGTH), this.withAttribute(input, 'maxLength'));
1709
- switch (inputType) {
1710
- case 'checkbox':
1711
- this.maybeSynchronize(() => this.state().value(), (value) => (input.checked = value));
1712
- break;
1713
- case 'radio':
1714
- this.maybeSynchronize(() => this.state().value(), (value) => {
1715
- // Although HTML behavior is to clear the input already, we do this just in case.
1716
- // It seems like it might be necessary in certain environments (e.g. Domino).
1717
- input.checked = input.value === value;
1718
- });
1719
- break;
1720
- case 'select':
1721
- this.maybeSynchronize(() => this.state().value(), (value) => {
1722
- // A select will not take a value unil the value's option has rendered.
1723
- afterNextRender(() => (input.value = value), { injector: this.injector });
1724
- });
1725
- break;
1726
- case 'number':
1727
- case 'range':
1728
- case 'datetime-local':
1729
- // This input type can receive a `number` or a `string`.
1730
- this.maybeSynchronize(() => this.state().value(), (value) => {
1731
- if (typeof value === 'number') {
1732
- input.valueAsNumber = value;
1733
- }
1734
- else {
1735
- input.value = value;
1736
- }
1737
- });
1738
- break;
1739
- case 'date':
1740
- case 'month':
1741
- case 'week':
1742
- case 'time':
1743
- // This input type can receive a `Date | null` or a `number` or a `string`.
1744
- this.maybeSynchronize(() => this.state().value(), (value) => {
1745
- if (isDateOrNull(value)) {
1746
- input.valueAsDate = value;
1747
- }
1748
- else if (typeof value === 'number') {
1749
- input.valueAsNumber = value;
1750
- }
1751
- else {
1752
- input.value = value;
1753
- }
1754
- });
1755
- break;
1756
- default:
1757
- this.maybeSynchronize(() => this.state().value(), (value) => {
1758
- input.value = value;
1759
- });
1760
- break;
1761
- }
1762
- }
1763
- /** Set up state synchronization between the field and a ControlValueAccessor. */
1764
- setupControlValueAccessor(cva) {
1765
- cva.registerOnChange((value) => this.state().value.set(value));
1766
- cva.registerOnTouched(() => this.state().markAsTouched());
1767
- this.maybeSynchronize(() => this.state().value(), (value) => cva.writeValue(value));
1768
- if (cva.setDisabledState) {
1769
- this.maybeSynchronize(() => this.state().disabled(), (value) => cva.setDisabledState(value));
1770
- }
1771
- cva.writeValue(this.state().value());
1772
- cva.setDisabledState?.(this.state().disabled());
1773
- }
1774
- /** Set up state synchronization between the field and a FormUiControl. */
1775
- setupCustomUiControl(cmp) {
1776
- // Handle the property side of the model binding. How we do this depends on the shape of the
1777
- // component. There are 2 options:
1778
- // * it provides a `value` model (most controls that edit a single value)
1779
- // * it provides a `checked` model with no `value` signal (custom checkbox)
1780
- let cleanupValue;
1781
- if (isFormValueControl(cmp)) {
1782
- // <custom-input [(value)]="state().value">
1783
- this.maybeSynchronize(() => this.state().value(), withInput(cmp.value));
1784
- cleanupValue = cmp.value.subscribe((newValue) => this.state().value.set(newValue));
1785
- }
1786
- else if (isFormCheckboxControl(cmp)) {
1787
- // <custom-checkbox [(checked)]="state().value" />
1788
- this.maybeSynchronize(() => this.state().value(), withInput(cmp.checked));
1789
- cleanupValue = cmp.checked.subscribe((newValue) => this.state().value.set(newValue));
1790
- }
1791
- else {
1792
- throw new Error(`Unknown custom control subtype`);
1793
- }
1794
- this.maybeSynchronize(() => this.state().name(), withInput(cmp.name));
1795
- this.maybeSynchronize(() => this.state().disabled(), withInput(cmp.disabled));
1796
- this.maybeSynchronize(() => this.state().disabledReasons(), withInput(cmp.disabledReasons));
1797
- this.maybeSynchronize(() => this.state().readonly(), withInput(cmp.readonly));
1798
- this.maybeSynchronize(() => this.state().hidden(), withInput(cmp.hidden));
1799
- this.maybeSynchronize(() => this.state().errors(), withInput(cmp.errors));
1800
- if (privateIsModelInput(cmp.touched) || privateIsSignalInput(cmp.touched)) {
1801
- this.maybeSynchronize(() => this.state().touched(), withInput(cmp.touched));
1802
- }
1803
- this.maybeSynchronize(() => this.state().dirty(), withInput(cmp.dirty));
1804
- this.maybeSynchronize(() => this.state().invalid(), withInput(cmp.invalid));
1805
- this.maybeSynchronize(() => this.state().pending(), withInput(cmp.pending));
1806
- this.maybeSynchronize(this.propertySource(REQUIRED), withInput(cmp.required));
1807
- this.maybeSynchronize(this.propertySource(MIN), withInput(cmp.min));
1808
- this.maybeSynchronize(this.propertySource(MIN_LENGTH), withInput(cmp.minLength));
1809
- this.maybeSynchronize(this.propertySource(MAX), withInput(cmp.max));
1810
- this.maybeSynchronize(this.propertySource(MAX_LENGTH), withInput(cmp.maxLength));
1811
- this.maybeSynchronize(this.propertySource(PATTERN), withInput(cmp.pattern));
1812
- let cleanupTouch;
1813
- let cleanupDefaultTouch;
1814
- if (privateIsModelInput(cmp.touched) || isOutputRef(cmp.touched)) {
1815
- cleanupTouch = cmp.touched.subscribe(() => this.state().markAsTouched());
1816
- }
1817
- else {
1818
- // If the component did not give us a touch event stream, use the standard touch logic,
1819
- // marking it touched when the focus moves from inside the host element to outside.
1820
- const listener = (event) => {
1821
- const newActiveEl = event.relatedTarget;
1822
- if (!this.el.nativeElement.contains(newActiveEl)) {
1823
- this.state().markAsTouched();
1824
- }
1825
- };
1826
- this.el.nativeElement.addEventListener('focusout', listener);
1827
- cleanupDefaultTouch = () => this.el.nativeElement.removeEventListener('focusout', listener);
1828
- }
1829
- // Cleanup for output binding subscriptions:
1830
- this.injector.get(DestroyRef).onDestroy(() => {
1831
- cleanupValue?.unsubscribe();
1832
- cleanupTouch?.unsubscribe();
1833
- cleanupDefaultTouch?.();
1834
- });
1835
- }
1836
- /** Synchronize a value from a reactive source to a given sink. */
1837
- maybeSynchronize(source, sink) {
1838
- if (!sink) {
1839
- return undefined;
1840
- }
1841
- const ref = effect(() => {
1842
- const value = source();
1843
- untracked(() => sink(value));
1844
- }, ...(ngDevMode ? [{ debugName: "ref", injector: this.injector }] : [{ injector: this.injector }]));
1845
- // Run the effect immediately to ensure sinks which are required inputs are set before they can
1846
- // be observed. See the note on `_field` for more details.
1847
- privateRunEffect(ref);
1848
- }
1849
- /** Creates a reactive value source by reading the given AggregateProperty from the field. */
1850
- propertySource(key) {
1851
- const metaSource = computed(() => this.state().hasProperty(key) ? this.state().property(key) : key.getInitial, ...(ngDevMode ? [{ debugName: "metaSource" }] : []));
1852
- return () => metaSource()?.();
1853
- }
1854
- /** Creates a (non-boolean) value sync that writes the given attribute of the given element. */
1855
- withAttribute(element, attribute) {
1856
- return (value) => {
1857
- if (value !== undefined) {
1858
- this.renderer.setAttribute(element, attribute, value.toString());
1859
- }
1860
- else {
1861
- this.renderer.removeAttribute(element, attribute);
1862
- }
1863
- };
1864
- }
1865
- /** Creates a boolean value sync that writes the given attribute of the given element. */
1866
- withBooleanAttribute(element, attribute) {
1867
- return (value) => {
1868
- if (value) {
1869
- this.renderer.setAttribute(element, attribute, '');
1870
- }
1871
- else {
1872
- this.renderer.removeAttribute(element, attribute);
1873
- }
1874
- };
1875
- }
1876
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0-next.5", ngImport: i0, type: Control, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1877
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0-next.5", type: Control, isStandalone: true, selector: "[control]", inputs: { _field: ["control", "_field"] }, providers: [
1878
- {
1879
- provide: NgControl,
1880
- useFactory: () => inject(Control).ngControl,
1881
- },
1882
- ], ngImport: i0 });
1470
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0-next.7", ngImport: i0, type: Control, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1471
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.0-next.7", type: Control, isStandalone: true, selector: "[control]", inputs: { field: { classPropertyName: "field", publicName: "control", isSignal: true, isRequired: true, transformFunction: null } }, providers: [{ provide: CONTROL, useExisting: Control }], ngImport: i0 });
1883
1472
  }
1884
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0-next.5", ngImport: i0, type: Control, decorators: [{
1473
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0-next.7", ngImport: i0, type: Control, decorators: [{
1885
1474
  type: Directive,
1886
- args: [{
1887
- selector: '[control]',
1888
- providers: [
1889
- {
1890
- provide: NgControl,
1891
- useFactory: () => inject(Control).ngControl,
1892
- },
1893
- ],
1894
- }]
1895
- }], propDecorators: { _field: [{
1896
- type: Input,
1897
- args: [{ required: true, alias: 'control' }]
1898
- }] } });
1899
- /** Creates a value sync from an input signal. */
1900
- function withInput(input) {
1901
- return input ? (value) => privateSetComponentInput(input, value) : undefined;
1902
- }
1903
- /**
1904
- * Checks whether the given component matches the contract for either FormValueControl or
1905
- * FormCheckboxControl.
1906
- */
1907
- function isFormUiControl(cmp) {
1908
- const castCmp = cmp;
1909
- return ((isFormValueControl(castCmp) || isFormCheckboxControl(castCmp)) &&
1910
- (castCmp.readonly === undefined || privateIsSignalInput(castCmp.readonly)) &&
1911
- (castCmp.disabled === undefined || privateIsSignalInput(castCmp.disabled)) &&
1912
- (castCmp.disabledReasons === undefined || privateIsSignalInput(castCmp.disabledReasons)) &&
1913
- (castCmp.errors === undefined || privateIsSignalInput(castCmp.errors)) &&
1914
- (castCmp.invalid === undefined || privateIsSignalInput(castCmp.invalid)) &&
1915
- (castCmp.pending === undefined || privateIsSignalInput(castCmp.pending)) &&
1916
- (castCmp.touched === undefined ||
1917
- privateIsModelInput(castCmp.touched) ||
1918
- privateIsSignalInput(castCmp.touched) ||
1919
- isOutputRef(castCmp.touched)) &&
1920
- (castCmp.dirty === undefined || privateIsSignalInput(castCmp.dirty)) &&
1921
- (castCmp.min === undefined || privateIsSignalInput(castCmp.min)) &&
1922
- (castCmp.minLength === undefined || privateIsSignalInput(castCmp.minLength)) &&
1923
- (castCmp.max === undefined || privateIsSignalInput(castCmp.max)) &&
1924
- (castCmp.maxLength === undefined || privateIsSignalInput(castCmp.maxLength)));
1925
- }
1926
- /** Checks whether the given FormUiControl is a FormValueControl. */
1927
- function isFormValueControl(cmp) {
1928
- return privateIsModelInput(cmp.value);
1929
- }
1930
- /** Checks whether the given FormUiControl is a FormCheckboxControl. */
1931
- function isFormCheckboxControl(cmp) {
1932
- return (privateIsModelInput(cmp.checked) &&
1933
- cmp.value === undefined);
1934
- }
1935
- /** Checks whether the given component has an input called `control`. */
1936
- function isShadowedControlComponent(cmp) {
1937
- const mirror = reflectComponentType(cmp.constructor);
1938
- return mirror?.inputs.some((input) => input.templateName === 'control') ?? false;
1939
- }
1940
- /** Checks whether the given object is an output ref. */
1941
- function isOutputRef(value) {
1942
- return value instanceof OutputEmitterRef || value instanceof EventEmitter;
1943
- }
1944
- /** Checks if a given value is a Date or null */
1945
- function isDateOrNull(value) {
1946
- return value === null || value instanceof Date;
1947
- }
1475
+ args: [{ selector: '[control]', providers: [{ provide: CONTROL, useExisting: Control }] }]
1476
+ }] });
1948
1477
 
1949
1478
  /**
1950
1479
  * `FieldContext` implementation, backed by a `FieldNode`.
@@ -2087,7 +1616,7 @@ class FieldPropertyState {
2087
1616
  }
2088
1617
 
2089
1618
  /**
2090
- * Proxy handler which implements `Field<T>` on top of `FieldNode`.
1619
+ * Proxy handler which implements `FieldTree<T>` on top of `FieldNode`.
2091
1620
  */
2092
1621
  const FIELD_PROXY_HANDLER = {
2093
1622
  get(getTgt, p) {
@@ -2095,8 +1624,8 @@ const FIELD_PROXY_HANDLER = {
2095
1624
  // First, check whether the requested property is a defined child node of this node.
2096
1625
  const child = tgt.structure.getChild(p);
2097
1626
  if (child !== undefined) {
2098
- // If so, return the child node's `Field` proxy, allowing the developer to continue navigating
2099
- // the form structure.
1627
+ // If so, return the child node's `FieldTree` proxy, allowing the developer to continue
1628
+ // navigating the form structure.
2100
1629
  return child.fieldProxy;
2101
1630
  }
2102
1631
  // Otherwise, we need to consider whether the properties they're accessing are related to array
@@ -2461,10 +1990,11 @@ class FieldSubmitState {
2461
1990
  serverErrors;
2462
1991
  constructor(node) {
2463
1992
  this.node = node;
2464
- this.serverErrors = linkedSignal({
2465
- source: this.node.structure.value,
2466
- computation: () => [],
2467
- });
1993
+ this.serverErrors = linkedSignal(...(ngDevMode ? [{ debugName: "serverErrors", source: this.node.structure.value,
1994
+ computation: () => [] }] : [{
1995
+ source: this.node.structure.value,
1996
+ computation: () => [],
1997
+ }]));
2468
1998
  }
2469
1999
  /**
2470
2000
  * Whether this form is currently in the process of being submitted.
@@ -2562,6 +2092,24 @@ class FieldNode {
2562
2092
  get name() {
2563
2093
  return this.nodeState.name;
2564
2094
  }
2095
+ get max() {
2096
+ return this.property(MAX);
2097
+ }
2098
+ get maxLength() {
2099
+ return this.property(MAX_LENGTH);
2100
+ }
2101
+ get min() {
2102
+ return this.property(MIN);
2103
+ }
2104
+ get minLength() {
2105
+ return this.property(MIN_LENGTH);
2106
+ }
2107
+ get pattern() {
2108
+ return this.property(PATTERN);
2109
+ }
2110
+ get required() {
2111
+ return this.property(REQUIRED);
2112
+ }
2565
2113
  property(prop) {
2566
2114
  return this.propertyState.get(prop);
2567
2115
  }
@@ -2895,8 +2443,8 @@ function form(...args) {
2895
2443
  * });
2896
2444
  * ```
2897
2445
  *
2898
- * When binding logic to the array items, the `Field` for the array item is passed as an additional
2899
- * argument. This can be used to reference other properties on the item.
2446
+ * When binding logic to the array items, the `FieldTree` for the array item is passed as an
2447
+ * additional argument. This can be used to reference other properties on the item.
2900
2448
  *
2901
2449
  * @example
2902
2450
  * ```
@@ -2970,14 +2518,14 @@ function applyWhenValue(path, predicate, schema) {
2970
2518
  applyWhen(path, ({ value }) => predicate(value()), schema);
2971
2519
  }
2972
2520
  /**
2973
- * Submits a given `Field` using the given action function and applies any server errors resulting
2974
- * from the action to the field. Server errors returned by the `action` will be integrated into the
2975
- * field as a `ValidationError` on the sub-field indicated by the `field` property of the server
2976
- * error.
2521
+ * Submits a given `FieldTree` using the given action function and applies any server errors
2522
+ * resulting from the action to the field. Server errors returned by the `action` will be integrated
2523
+ * into the field as a `ValidationError` on the sub-field indicated by the `field` property of the
2524
+ * server error.
2977
2525
  *
2978
2526
  * @example
2979
2527
  * ```
2980
- * async function registerNewUser(registrationForm: Field<{username: string, password: string}>) {
2528
+ * async function registerNewUser(registrationForm: FieldTree<{username: string, password: string}>) {
2981
2529
  * const result = await myClient.registerNewUser(registrationForm().value());
2982
2530
  * if (result.errorCode === myClient.ErrorCode.USERNAME_TAKEN) {
2983
2531
  * return [{
@@ -3048,7 +2596,7 @@ function setServerErrors(submittedField, errors) {
3048
2596
  * Creates a `Schema` that adds logic rules to a form.
3049
2597
  * @param fn A **non-reactive** function that sets up reactive logic rules for the form.
3050
2598
  * @returns A schema object that implements the given logic.
3051
- * @template TValue The value type of a `Field` that this schema binds to.
2599
+ * @template TValue The value type of a `FieldTree` that this schema binds to.
3052
2600
  *
3053
2601
  * @category structure
3054
2602
  * @experimental 21.0.0
@@ -3642,5 +3190,5 @@ function standardIssueToFormTreeError(field, issue) {
3642
3190
  return addDefaultField(standardSchemaError(issue), target);
3643
3191
  }
3644
3192
 
3645
- export { AggregateProperty, Control, CustomValidationError, EmailValidationError, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MinLengthValidationError, MinValidationError, NgValidationError, PATTERN, PatternValidationError, Property, REQUIRED, RequiredValidationError, StandardSchemaValidationError, aggregateProperty, andProperty, apply, applyEach, applyWhen, applyWhenValue, createProperty, customError, disabled, email, emailError, form, hidden, listProperty, max, maxError, maxLength, maxLengthError, maxProperty, min, minError, minLength, minLengthError, minProperty, orProperty, pattern, patternError, property, readonly, reducedProperty, required, requiredError, schema, standardSchemaError, submit, validate, validateAsync, validateHttp, validateStandardSchema, validateTree };
3193
+ export { AggregateProperty, CONTROL, Control, CustomValidationError, EmailValidationError, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MinLengthValidationError, MinValidationError, NgValidationError, PATTERN, PatternValidationError, Property, REQUIRED, RequiredValidationError, StandardSchemaValidationError, aggregateProperty, andProperty, apply, applyEach, applyWhen, applyWhenValue, createProperty, customError, disabled, email, emailError, form, hidden, listProperty, max, maxError, maxLength, maxLengthError, maxProperty, min, minError, minLength, minLengthError, minProperty, orProperty, pattern, patternError, property, readonly, reducedProperty, required, requiredError, schema, standardSchemaError, submit, validate, validateAsync, validateHttp, validateStandardSchema, validateTree };
3646
3194
  //# sourceMappingURL=signals.mjs.map