@angular/forms 21.2.0-next.3 → 21.2.0-rc.0

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,11 +1,11 @@
1
1
  /**
2
- * @license Angular v21.2.0-next.3
2
+ * @license Angular v21.2.0-rc.0
3
3
  * (c) 2010-2026 Google LLC. https://angular.dev/
4
4
  * License: MIT
5
5
  */
6
6
 
7
7
  import * as i0 from '@angular/core';
8
- import { InjectionToken, ɵisPromise as _isPromise, resource, signal, linkedSignal, inject, ɵRuntimeError as _RuntimeError, untracked, input, Renderer2, DestroyRef, computed, Injector, ElementRef, afterRenderEffect, effect, Directive } from '@angular/core';
8
+ import { InjectionToken, ɵisPromise as _isPromise, resource, linkedSignal, inject, ɵRuntimeError as _RuntimeError, untracked, input, Renderer2, DestroyRef, computed, Injector, ElementRef, signal, afterRenderEffect, effect, ɵformatRuntimeError as _formatRuntimeError, Directive } from '@angular/core';
9
9
  import { assertPathIsCurrent, FieldPathNode, addDefaultField, metadata, createMetadataKey, MAX, MAX_LENGTH, MIN, MIN_LENGTH, PATTERN, REQUIRED, createManagedMetadataKey, DEBOUNCER, signalErrorsToValidationErrors, submit } from './_validation_errors-chunk.mjs';
10
10
  export { MetadataKey, MetadataReducer, apply, applyEach, applyWhen, applyWhenValue, form, schema } from './_validation_errors-chunk.mjs';
11
11
  import { httpResource } from '@angular/common/http';
@@ -155,6 +155,9 @@ class PatternValidationError extends BaseNgValidationError {
155
155
  class EmailValidationError extends BaseNgValidationError {
156
156
  kind = 'email';
157
157
  }
158
+ class NativeInputParseError extends BaseNgValidationError {
159
+ kind = 'parse';
160
+ }
158
161
  const NgValidationError = BaseNgValidationError;
159
162
 
160
163
  const EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
@@ -467,38 +470,53 @@ function immediate() {}
467
470
 
468
471
  const FORM_FIELD_PARSE_ERRORS = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FORM_FIELD_PARSE_ERRORS' : '');
469
472
 
473
+ function createParser(getValue, setValue, parse) {
474
+ const errors = linkedSignal({
475
+ ...(ngDevMode ? {
476
+ debugName: "errors"
477
+ } : {}),
478
+ source: getValue,
479
+ computation: () => []
480
+ });
481
+ const setRawValue = rawValue => {
482
+ const result = parse(rawValue);
483
+ errors.set(result.errors ?? []);
484
+ if (result.value !== undefined) {
485
+ setValue(result.value);
486
+ }
487
+ };
488
+ return {
489
+ errors: errors.asReadonly(),
490
+ setRawValue
491
+ };
492
+ }
493
+
470
494
  function transformedValue(value, options) {
471
495
  const {
472
496
  parse,
473
497
  format
474
498
  } = options;
475
- const parseErrors = signal([], ...(ngDevMode ? [{
476
- debugName: "parseErrors"
477
- }] : []));
478
- const rawValue = linkedSignal(() => format(value()), ...(ngDevMode ? [{
479
- debugName: "rawValue"
480
- }] : []));
499
+ const parser = createParser(value, value.set, parse);
481
500
  const formFieldParseErrors = inject(FORM_FIELD_PARSE_ERRORS, {
482
501
  self: true,
483
502
  optional: true
484
503
  });
485
504
  if (formFieldParseErrors) {
486
- formFieldParseErrors.set(parseErrors);
505
+ formFieldParseErrors.set(parser.errors);
487
506
  }
507
+ const rawValue = linkedSignal(() => format(value()), ...(ngDevMode ? [{
508
+ debugName: "rawValue"
509
+ }] : []));
488
510
  const result = rawValue;
511
+ result.parseErrors = parser.errors;
489
512
  const originalSet = result.set.bind(result);
490
513
  result.set = newRawValue => {
491
- const result = parse(newRawValue);
492
- parseErrors.set(result.errors ?? []);
493
- if (result.value !== undefined) {
494
- value.set(result.value);
495
- }
514
+ parser.setRawValue(newRawValue);
496
515
  originalSet(newRawValue);
497
516
  };
498
517
  result.update = updateFn => {
499
518
  result.set(updateFn(rawValue()));
500
519
  };
501
- result.parseErrors = parseErrors.asReadonly();
502
520
  return result;
503
521
  }
504
522
 
@@ -621,29 +639,46 @@ function isTextualFormElement(element) {
621
639
  return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';
622
640
  }
623
641
  function getNativeControlValue(element, currentValue) {
642
+ let modelValue;
643
+ if (element.validity.badInput) {
644
+ return {
645
+ errors: [new NativeInputParseError()]
646
+ };
647
+ }
624
648
  switch (element.type) {
625
649
  case 'checkbox':
626
- return element.checked;
650
+ return {
651
+ value: element.checked
652
+ };
627
653
  case 'number':
628
654
  case 'range':
629
655
  case 'datetime-local':
630
- if (typeof untracked(currentValue) === 'number') {
631
- return element.valueAsNumber;
656
+ modelValue = untracked(currentValue);
657
+ if (typeof modelValue === 'number' || modelValue === null) {
658
+ return {
659
+ value: element.value === '' ? null : element.valueAsNumber
660
+ };
632
661
  }
633
662
  break;
634
663
  case 'date':
635
664
  case 'month':
636
665
  case 'time':
637
666
  case 'week':
638
- const value = untracked(currentValue);
639
- if (value === null || value instanceof Date) {
640
- return element.valueAsDate;
641
- } else if (typeof value === 'number') {
642
- return element.valueAsNumber;
667
+ modelValue = untracked(currentValue);
668
+ if (modelValue === null || modelValue instanceof Date) {
669
+ return {
670
+ value: element.valueAsDate
671
+ };
672
+ } else if (typeof modelValue === 'number') {
673
+ return {
674
+ value: element.valueAsNumber
675
+ };
643
676
  }
644
677
  break;
645
678
  }
646
- return element.value;
679
+ return {
680
+ value: element.value
681
+ };
647
682
  }
648
683
  function setNativeControlValue(element, value) {
649
684
  switch (element.type) {
@@ -659,6 +694,9 @@ function setNativeControlValue(element, value) {
659
694
  if (typeof value === 'number') {
660
695
  setNativeNumberControlValue(element, value);
661
696
  return;
697
+ } else if (value === null) {
698
+ element.value = '';
699
+ return;
662
700
  }
663
701
  break;
664
702
  case 'date':
@@ -803,13 +841,12 @@ function isRelevantSelectMutation(mutation) {
803
841
  return false;
804
842
  }
805
843
 
806
- function nativeControlCreate(host, parent) {
844
+ function nativeControlCreate(host, parent, parseErrorsSource) {
807
845
  let updateMode = false;
808
846
  const input = parent.nativeFormElement;
809
- host.listenToDom('input', () => {
810
- const state = parent.state();
811
- state.controlValue.set(getNativeControlValue(input, state.value));
812
- });
847
+ const parser = createParser(() => parent.state().value(), rawValue => parent.state().controlValue.set(rawValue), () => getNativeControlValue(input, parent.state().value));
848
+ parseErrorsSource.set(parser.errors);
849
+ host.listenToDom('input', () => parser.setRawValue(undefined));
813
850
  host.listenToDom('blur', () => parent.state().markAsTouched());
814
851
  parent.registerAsBinding();
815
852
  if (input.tagName === 'SELECT') {
@@ -843,15 +880,15 @@ function nativeControlCreate(host, parent) {
843
880
  const ɵNgFieldDirective = Symbol();
844
881
  const FORM_FIELD = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FORM_FIELD' : '');
845
882
  class FormField {
846
- fieldTree = input.required({
883
+ field = input.required({
847
884
  ...(ngDevMode ? {
848
- debugName: "fieldTree"
885
+ debugName: "field"
849
886
  } : {}),
850
887
  alias: 'formField'
851
888
  });
852
889
  renderer = inject(Renderer2);
853
890
  destroyRef = inject(DestroyRef);
854
- state = computed(() => this.fieldTree()(), ...(ngDevMode ? [{
891
+ state = computed(() => this.field()(), ...(ngDevMode ? [{
855
892
  debugName: "state"
856
893
  }] : []));
857
894
  injector = inject(Injector);
@@ -877,7 +914,7 @@ class FormField {
877
914
  }
878
915
  parseErrors = computed(() => this.parseErrorsSource()?.().map(err => ({
879
916
  ...err,
880
- fieldTree: untracked(this.fieldTree),
917
+ fieldTree: untracked(this.state).fieldTree,
881
918
  formField: this
882
919
  })) ?? [], ...(ngDevMode ? [{
883
920
  debugName: "parseErrors"
@@ -917,12 +954,12 @@ class FormField {
917
954
  }
918
955
  registerAsBinding(bindingOptions) {
919
956
  if (this.isFieldBinding) {
920
- throw new _RuntimeError(1913, ngDevMode && 'FormField already registered as a binding');
957
+ throw new _RuntimeError(1913, typeof ngDevMode !== 'undefined' && ngDevMode && 'FormField already registered as a binding');
921
958
  }
922
959
  this.isFieldBinding = true;
923
960
  this.installClassBindingEffect();
924
961
  if (bindingOptions?.focus) {
925
- this.focuser = bindingOptions.focus;
962
+ this.focuser = focusOptions => bindingOptions.focus(focusOptions);
926
963
  }
927
964
  effect(onCleanup => {
928
965
  const fieldNode = this.state();
@@ -933,6 +970,17 @@ class FormField {
933
970
  }, {
934
971
  injector: this.injector
935
972
  });
973
+ if (typeof ngDevMode !== 'undefined' && ngDevMode) {
974
+ effect(() => {
975
+ const fieldNode = this.state();
976
+ if (fieldNode.hidden()) {
977
+ const path = fieldNode.structure.pathKeys().join('.') || '<root>';
978
+ console.warn(_formatRuntimeError(1916, `Field '${path}' is hidden but is being rendered. ` + `Hidden fields should be removed from the DOM using @if.`));
979
+ }
980
+ }, {
981
+ injector: this.injector
982
+ });
983
+ }
936
984
  }
937
985
  [ɵNgFieldDirective];
938
986
  ɵngControlCreate(host) {
@@ -944,9 +992,9 @@ class FormField {
944
992
  } else if (host.customControl) {
945
993
  this.ɵngControlUpdate = customControlCreate(host, this);
946
994
  } else if (this.elementIsNativeFormElement) {
947
- this.ɵngControlUpdate = nativeControlCreate(host, this);
995
+ this.ɵngControlUpdate = nativeControlCreate(host, this, this.parseErrorsSource);
948
996
  } else {
949
- throw new _RuntimeError(1914, ngDevMode && `${host.descriptor} is an invalid [formField] directive host. The host must be a native form control ` + `(such as <input>', '<select>', or '<textarea>') or a custom form control with a 'value' or ` + `'checked' model.`);
997
+ throw new _RuntimeError(1914, typeof ngDevMode !== 'undefined' && ngDevMode && `${host.descriptor} is an invalid [formField] directive host. The host must be a native form control ` + `(such as <input>', '<select>', or '<textarea>') or a custom form control with a 'value' or ` + `'checked' model.`);
950
998
  }
951
999
  }
952
1000
  ɵngControlUpdate;
@@ -972,7 +1020,7 @@ class FormField {
972
1020
  }
973
1021
  static ɵfac = i0.ɵɵngDeclareFactory({
974
1022
  minVersion: "12.0.0",
975
- version: "21.2.0-next.3",
1023
+ version: "21.2.0-rc.0",
976
1024
  ngImport: i0,
977
1025
  type: FormField,
978
1026
  deps: [],
@@ -980,13 +1028,13 @@ class FormField {
980
1028
  });
981
1029
  static ɵdir = i0.ɵɵngDeclareDirective({
982
1030
  minVersion: "17.1.0",
983
- version: "21.2.0-next.3",
1031
+ version: "21.2.0-rc.0",
984
1032
  type: FormField,
985
1033
  isStandalone: true,
986
1034
  selector: "[formField]",
987
1035
  inputs: {
988
- fieldTree: {
989
- classPropertyName: "fieldTree",
1036
+ field: {
1037
+ classPropertyName: "field",
990
1038
  publicName: "formField",
991
1039
  isSignal: true,
992
1040
  isRequired: true,
@@ -1012,7 +1060,7 @@ class FormField {
1012
1060
  }
1013
1061
  i0.ɵɵngDeclareClassMetadata({
1014
1062
  minVersion: "12.0.0",
1015
- version: "21.2.0-next.3",
1063
+ version: "21.2.0-rc.0",
1016
1064
  ngImport: i0,
1017
1065
  type: FormField,
1018
1066
  decorators: [{
@@ -1033,7 +1081,7 @@ i0.ɵɵngDeclareClassMetadata({
1033
1081
  }]
1034
1082
  }],
1035
1083
  propDecorators: {
1036
- fieldTree: [{
1084
+ field: [{
1037
1085
  type: i0.Input,
1038
1086
  args: [{
1039
1087
  isSignal: true,
@@ -1057,7 +1105,7 @@ class FormRoot {
1057
1105
  }
1058
1106
  static ɵfac = i0.ɵɵngDeclareFactory({
1059
1107
  minVersion: "12.0.0",
1060
- version: "21.2.0-next.3",
1108
+ version: "21.2.0-rc.0",
1061
1109
  ngImport: i0,
1062
1110
  type: FormRoot,
1063
1111
  deps: [],
@@ -1065,7 +1113,7 @@ class FormRoot {
1065
1113
  });
1066
1114
  static ɵdir = i0.ɵɵngDeclareDirective({
1067
1115
  minVersion: "17.1.0",
1068
- version: "21.2.0-next.3",
1116
+ version: "21.2.0-rc.0",
1069
1117
  type: FormRoot,
1070
1118
  isStandalone: true,
1071
1119
  selector: "form[formRoot]",
@@ -1091,7 +1139,7 @@ class FormRoot {
1091
1139
  }
1092
1140
  i0.ɵɵngDeclareClassMetadata({
1093
1141
  minVersion: "12.0.0",
1094
- version: "21.2.0-next.3",
1142
+ version: "21.2.0-rc.0",
1095
1143
  ngImport: i0,
1096
1144
  type: FormRoot,
1097
1145
  decorators: [{
@@ -1116,5 +1164,5 @@ i0.ɵɵngDeclareClassMetadata({
1116
1164
  }
1117
1165
  });
1118
1166
 
1119
- export { BaseNgValidationError, EmailValidationError, FORM_FIELD, FormField, FormRoot, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MinLengthValidationError, MinValidationError, NgValidationError, PATTERN, PatternValidationError, REQUIRED, RequiredValidationError, StandardSchemaValidationError, createManagedMetadataKey, createMetadataKey, debounce, disabled, email, emailError, hidden, max, maxError, maxLength, maxLengthError, metadata, min, minError, minLength, minLengthError, pattern, patternError, provideSignalFormsConfig, readonly, required, requiredError, standardSchemaError, submit, transformedValue, validate, validateAsync, validateHttp, validateStandardSchema, validateTree, ɵNgFieldDirective };
1167
+ export { BaseNgValidationError, EmailValidationError, FORM_FIELD, FormField, FormRoot, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MinLengthValidationError, MinValidationError, NativeInputParseError, NgValidationError, PATTERN, PatternValidationError, REQUIRED, RequiredValidationError, StandardSchemaValidationError, createManagedMetadataKey, createMetadataKey, debounce, disabled, email, emailError, hidden, max, maxError, maxLength, maxLengthError, metadata, min, minError, minLength, minLengthError, pattern, patternError, provideSignalFormsConfig, readonly, required, requiredError, standardSchemaError, submit, transformedValue, validate, validateAsync, validateHttp, validateStandardSchema, validateTree, ɵNgFieldDirective };
1120
1168
  //# sourceMappingURL=signals.mjs.map