@angular/forms 21.0.0-next.6 → 21.0.0-next.8

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.6
3
- * (c) 2010-2025 Google LLC. https://angular.io/
2
+ * @license Angular v21.0.0-next.8
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,525 +1425,58 @@ 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 Field} 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 FIELD = new InjectionToken(typeof ngDevMode !== undefined && ngDevMode ? 'FIELD' : '');
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
- class Control {
1564
- /** The injector for this component. */
1452
+ class Field {
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" }] : []));
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
- // (as opposed to just passing the field through to its `control` input).
1461
+ // (as opposed to just passing the field through to its `field` input).
1638
1462
  effect((onCleanup) => {
1639
1463
  const fieldNode = this.state();
1640
- fieldNode.nodeState.controls.update((controls) => [...controls, this]);
1464
+ fieldNode.nodeState.fieldBindings.update((controls) => [
1465
+ ...controls,
1466
+ this,
1467
+ ]);
1641
1468
  onCleanup(() => {
1642
- fieldNode.nodeState.controls.update((controls) => controls.filter((c) => c !== this));
1469
+ fieldNode.nodeState.fieldBindings.update((controls) => controls.filter((c) => c !== this));
1643
1470
  });
1644
1471
  }, { injector: this.injector });
1645
1472
  }
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.6", ngImport: i0, type: Control, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1877
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0-next.6", type: Control, isStandalone: true, selector: "[control]", inputs: { _field: ["control", "_field"] }, providers: [
1878
- {
1879
- provide: NgControl,
1880
- useFactory: () => inject(Control).ngControl,
1881
- },
1882
- ], ngImport: i0 });
1473
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0-next.8", ngImport: i0, type: Field, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1474
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.0-next.8", type: Field, isStandalone: true, selector: "[field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null } }, providers: [{ provide: FIELD, useExisting: Field }], ngImport: i0 });
1883
1475
  }
1884
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0-next.6", ngImport: i0, type: Control, decorators: [{
1476
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0-next.8", ngImport: i0, type: Field, decorators: [{
1885
1477
  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
- }
1478
+ args: [{ selector: '[field]', providers: [{ provide: FIELD, useExisting: Field }] }]
1479
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }] } });
1948
1480
 
1949
1481
  /**
1950
1482
  * `FieldContext` implementation, backed by a `FieldNode`.
@@ -2087,7 +1619,7 @@ class FieldPropertyState {
2087
1619
  }
2088
1620
 
2089
1621
  /**
2090
- * Proxy handler which implements `Field<T>` on top of `FieldNode`.
1622
+ * Proxy handler which implements `FieldTree<T>` on top of `FieldNode`.
2091
1623
  */
2092
1624
  const FIELD_PROXY_HANDLER = {
2093
1625
  get(getTgt, p) {
@@ -2095,8 +1627,8 @@ const FIELD_PROXY_HANDLER = {
2095
1627
  // First, check whether the requested property is a defined child node of this node.
2096
1628
  const child = tgt.structure.getChild(p);
2097
1629
  if (child !== undefined) {
2098
- // If so, return the child node's `Field` proxy, allowing the developer to continue navigating
2099
- // the form structure.
1630
+ // If so, return the child node's `FieldTree` proxy, allowing the developer to continue
1631
+ // navigating the form structure.
2100
1632
  return child.fieldProxy;
2101
1633
  }
2102
1634
  // Otherwise, we need to consider whether the properties they're accessing are related to array
@@ -2554,8 +2086,8 @@ class FieldNode {
2554
2086
  get readonly() {
2555
2087
  return this.nodeState.readonly;
2556
2088
  }
2557
- get controls() {
2558
- return this.nodeState.controls;
2089
+ get fieldBindings() {
2090
+ return this.nodeState.fieldBindings;
2559
2091
  }
2560
2092
  get submitting() {
2561
2093
  return this.submitState.submitting;
@@ -2563,6 +2095,24 @@ class FieldNode {
2563
2095
  get name() {
2564
2096
  return this.nodeState.name;
2565
2097
  }
2098
+ get max() {
2099
+ return this.property(MAX);
2100
+ }
2101
+ get maxLength() {
2102
+ return this.property(MAX_LENGTH);
2103
+ }
2104
+ get min() {
2105
+ return this.property(MIN);
2106
+ }
2107
+ get minLength() {
2108
+ return this.property(MIN_LENGTH);
2109
+ }
2110
+ get pattern() {
2111
+ return this.property(PATTERN);
2112
+ }
2113
+ get required() {
2114
+ return this.property(REQUIRED);
2115
+ }
2566
2116
  property(prop) {
2567
2117
  return this.propertyState.get(prop);
2568
2118
  }
@@ -2656,8 +2206,8 @@ class FieldNodeState {
2656
2206
  markAsUntouched() {
2657
2207
  this.selfTouched.set(false);
2658
2208
  }
2659
- /** The UI controls the field is currently bound to. */
2660
- controls = signal([], ...(ngDevMode ? [{ debugName: "controls" }] : []));
2209
+ /** The {@link Field} directives that bind this field to a UI control. */
2210
+ fieldBindings = signal([], ...(ngDevMode ? [{ debugName: "fieldBindings" }] : []));
2661
2211
  constructor(node) {
2662
2212
  this.node = node;
2663
2213
  }
@@ -2896,8 +2446,8 @@ function form(...args) {
2896
2446
  * });
2897
2447
  * ```
2898
2448
  *
2899
- * When binding logic to the array items, the `Field` for the array item is passed as an additional
2900
- * argument. This can be used to reference other properties on the item.
2449
+ * When binding logic to the array items, the `FieldTree` for the array item is passed as an
2450
+ * additional argument. This can be used to reference other properties on the item.
2901
2451
  *
2902
2452
  * @example
2903
2453
  * ```
@@ -2971,14 +2521,14 @@ function applyWhenValue(path, predicate, schema) {
2971
2521
  applyWhen(path, ({ value }) => predicate(value()), schema);
2972
2522
  }
2973
2523
  /**
2974
- * Submits a given `Field` using the given action function and applies any server errors resulting
2975
- * from the action to the field. Server errors returned by the `action` will be integrated into the
2976
- * field as a `ValidationError` on the sub-field indicated by the `field` property of the server
2977
- * error.
2524
+ * Submits a given `FieldTree` using the given action function and applies any server errors
2525
+ * resulting from the action to the field. Server errors returned by the `action` will be integrated
2526
+ * into the field as a `ValidationError` on the sub-field indicated by the `field` property of the
2527
+ * server error.
2978
2528
  *
2979
2529
  * @example
2980
2530
  * ```
2981
- * async function registerNewUser(registrationForm: Field<{username: string, password: string}>) {
2531
+ * async function registerNewUser(registrationForm: FieldTree<{username: string, password: string}>) {
2982
2532
  * const result = await myClient.registerNewUser(registrationForm().value());
2983
2533
  * if (result.errorCode === myClient.ErrorCode.USERNAME_TAKEN) {
2984
2534
  * return [{
@@ -3049,7 +2599,7 @@ function setServerErrors(submittedField, errors) {
3049
2599
  * Creates a `Schema` that adds logic rules to a form.
3050
2600
  * @param fn A **non-reactive** function that sets up reactive logic rules for the form.
3051
2601
  * @returns A schema object that implements the given logic.
3052
- * @template TValue The value type of a `Field` that this schema binds to.
2602
+ * @template TValue The value type of a `FieldTree` that this schema binds to.
3053
2603
  *
3054
2604
  * @category structure
3055
2605
  * @experimental 21.0.0
@@ -3643,5 +3193,5 @@ function standardIssueToFormTreeError(field, issue) {
3643
3193
  return addDefaultField(standardSchemaError(issue), target);
3644
3194
  }
3645
3195
 
3646
- 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 };
3196
+ export { AggregateProperty, CustomValidationError, EmailValidationError, FIELD, Field, 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 };
3647
3197
  //# sourceMappingURL=signals.mjs.map