@angular/forms 21.2.0-next.0 → 21.2.0-next.1

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,5 +1,5 @@
1
1
  /**
2
- * @license Angular v21.2.0-next.0
2
+ * @license Angular v21.2.0-next.1
3
3
  * (c) 2010-2026 Google LLC. https://angular.dev/
4
4
  * License: MIT
5
5
  */
@@ -647,10 +647,19 @@ class FieldValidationState {
647
647
  }, ...(ngDevMode ? [{
648
648
  debugName: "asyncErrors"
649
649
  }] : []));
650
- errors = computed(() => [...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], ...(ngDevMode ? [{
650
+ parseErrors = computed(() => this.node.formFieldBindings().flatMap(field => field.parseErrors()), ...(ngDevMode ? [{
651
+ debugName: "parseErrors"
652
+ }] : []));
653
+ errors = computed(() => [...this.parseErrors(), ...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], ...(ngDevMode ? [{
651
654
  debugName: "errors"
652
655
  }] : []));
653
- errorSummary = computed(() => this.node.structure.reduceChildren(this.errors(), (child, result) => [...result, ...child.errorSummary()]), ...(ngDevMode ? [{
656
+ errorSummary = computed(() => {
657
+ const errors = this.node.structure.reduceChildren(this.errors(), (child, result) => [...result, ...child.errorSummary()]);
658
+ if (typeof ngServerMode === 'undefined' || !ngServerMode) {
659
+ untracked(() => errors.sort(compareErrorPosition));
660
+ }
661
+ return errors;
662
+ }, ...(ngDevMode ? [{
654
663
  debugName: "errorSummary"
655
664
  }] : []));
656
665
  pending = computed(() => this.node.structure.reduceChildren(this.asyncErrors().includes('pending'), (child, value) => value || child.validationState.asyncErrors().includes('pending')), ...(ngDevMode ? [{
@@ -701,6 +710,20 @@ function addDefaultField(errors, fieldTree) {
701
710
  }
702
711
  return errors;
703
712
  }
713
+ function getFirstBoundElement(error) {
714
+ if (error.formField) return error.formField.element;
715
+ return error.fieldTree().formFieldBindings().reduce((el, binding) => {
716
+ if (!el || !binding.element) return el ?? binding.element;
717
+ return el.compareDocumentPosition(binding.element) & Node.DOCUMENT_POSITION_PRECEDING ? binding.element : el;
718
+ }, undefined);
719
+ }
720
+ function compareErrorPosition(a, b) {
721
+ const aEl = getFirstBoundElement(a);
722
+ const bEl = getFirstBoundElement(b);
723
+ if (aEl === bEl) return 0;
724
+ if (aEl === undefined || bEl === undefined) return aEl === undefined ? 1 : -1;
725
+ return aEl.compareDocumentPosition(bEl) & Node.DOCUMENT_POSITION_PRECEDING ? 1 : -1;
726
+ }
704
727
 
705
728
  const DEBOUNCER = createMetadataKey();
706
729
 
@@ -1214,6 +1237,9 @@ class FieldNode {
1214
1237
  get errors() {
1215
1238
  return this.validationState.errors;
1216
1239
  }
1240
+ get parseErrors() {
1241
+ return this.validationState.parseErrors;
1242
+ }
1217
1243
  get errorSummary() {
1218
1244
  return this.validationState.errorSummary;
1219
1245
  }
@@ -1557,8 +1583,11 @@ function applyWhenValue(path, predicate, schema) {
1557
1583
  }
1558
1584
  async function submit(form, action) {
1559
1585
  const node = form();
1560
- markAllAsTouched(node);
1561
- if (node.invalid()) {
1586
+ const invalid = untracked(() => {
1587
+ markAllAsTouched(node);
1588
+ return node.invalid();
1589
+ });
1590
+ if (invalid) {
1562
1591
  return;
1563
1592
  }
1564
1593
  node.submitState.selfSubmitting.set(true);
@@ -1592,6 +1621,9 @@ function schema(fn) {
1592
1621
  return SchemaImpl.create(fn);
1593
1622
  }
1594
1623
  function markAllAsTouched(node) {
1624
+ if (node.validationState.shouldSkipValidation()) {
1625
+ return;
1626
+ }
1595
1627
  node.markAsTouched();
1596
1628
  for (const child of node.structure.children()) {
1597
1629
  markAllAsTouched(child);