@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.
- package/fesm2022/_validation_errors-chunk.mjs +1 -1
- package/fesm2022/_validation_errors-chunk.mjs.map +1 -1
- package/fesm2022/forms.mjs +128 -128
- package/fesm2022/forms.mjs.map +1 -1
- package/fesm2022/signals-compat.mjs +3 -5
- package/fesm2022/signals-compat.mjs.map +1 -1
- package/fesm2022/signals.mjs +95 -47
- package/fesm2022/signals.mjs.map +1 -1
- package/package.json +4 -4
- package/resources/code-examples.db +0 -0
- package/types/_structure-chunk.d.ts +26 -8
- package/types/forms.d.ts +1 -1
- package/types/signals-compat.d.ts +1 -1
- package/types/signals.d.ts +20 -10
package/fesm2022/signals.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v21.2.0-
|
|
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,
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
650
|
+
return {
|
|
651
|
+
value: element.checked
|
|
652
|
+
};
|
|
627
653
|
case 'number':
|
|
628
654
|
case 'range':
|
|
629
655
|
case 'datetime-local':
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
639
|
-
if (
|
|
640
|
-
return
|
|
641
|
-
|
|
642
|
-
|
|
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
|
|
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
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
-
|
|
883
|
+
field = input.required({
|
|
847
884
|
...(ngDevMode ? {
|
|
848
|
-
debugName: "
|
|
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.
|
|
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-
|
|
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-
|
|
1031
|
+
version: "21.2.0-rc.0",
|
|
984
1032
|
type: FormField,
|
|
985
1033
|
isStandalone: true,
|
|
986
1034
|
selector: "[formField]",
|
|
987
1035
|
inputs: {
|
|
988
|
-
|
|
989
|
-
classPropertyName: "
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|