@angular/forms 21.2.0-next.3 → 21.2.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/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 +105 -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 +30 -12
package/fesm2022/signals.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v21.2.0
|
|
2
|
+
* @license Angular v21.2.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';
|
|
@@ -68,6 +68,15 @@ function isEmpty(value) {
|
|
|
68
68
|
}
|
|
69
69
|
return value === '' || value === false || value == null;
|
|
70
70
|
}
|
|
71
|
+
function normalizeErrors(error) {
|
|
72
|
+
if (error === undefined) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(error)) {
|
|
76
|
+
return error;
|
|
77
|
+
}
|
|
78
|
+
return [error];
|
|
79
|
+
}
|
|
71
80
|
|
|
72
81
|
function validate(path, logic) {
|
|
73
82
|
assertPathIsCurrent(path);
|
|
@@ -155,6 +164,9 @@ class PatternValidationError extends BaseNgValidationError {
|
|
|
155
164
|
class EmailValidationError extends BaseNgValidationError {
|
|
156
165
|
kind = 'email';
|
|
157
166
|
}
|
|
167
|
+
class NativeInputParseError extends BaseNgValidationError {
|
|
168
|
+
kind = 'parse';
|
|
169
|
+
}
|
|
158
170
|
const NgValidationError = BaseNgValidationError;
|
|
159
171
|
|
|
160
172
|
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 +479,54 @@ function immediate() {}
|
|
|
467
479
|
|
|
468
480
|
const FORM_FIELD_PARSE_ERRORS = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FORM_FIELD_PARSE_ERRORS' : '');
|
|
469
481
|
|
|
482
|
+
function createParser(getValue, setValue, parse) {
|
|
483
|
+
const errors = linkedSignal({
|
|
484
|
+
...(ngDevMode ? {
|
|
485
|
+
debugName: "errors"
|
|
486
|
+
} : {}),
|
|
487
|
+
source: getValue,
|
|
488
|
+
computation: () => []
|
|
489
|
+
});
|
|
490
|
+
const setRawValue = rawValue => {
|
|
491
|
+
const result = parse(rawValue);
|
|
492
|
+
errors.set(normalizeErrors(result.error));
|
|
493
|
+
if (result.value !== undefined) {
|
|
494
|
+
setValue(result.value);
|
|
495
|
+
}
|
|
496
|
+
errors.set(normalizeErrors(result.error));
|
|
497
|
+
};
|
|
498
|
+
return {
|
|
499
|
+
errors: errors.asReadonly(),
|
|
500
|
+
setRawValue
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
470
504
|
function transformedValue(value, options) {
|
|
471
505
|
const {
|
|
472
506
|
parse,
|
|
473
507
|
format
|
|
474
508
|
} = options;
|
|
475
|
-
const
|
|
476
|
-
debugName: "parseErrors"
|
|
477
|
-
}] : []));
|
|
478
|
-
const rawValue = linkedSignal(() => format(value()), ...(ngDevMode ? [{
|
|
479
|
-
debugName: "rawValue"
|
|
480
|
-
}] : []));
|
|
509
|
+
const parser = createParser(value, value.set, parse);
|
|
481
510
|
const formFieldParseErrors = inject(FORM_FIELD_PARSE_ERRORS, {
|
|
482
511
|
self: true,
|
|
483
512
|
optional: true
|
|
484
513
|
});
|
|
485
514
|
if (formFieldParseErrors) {
|
|
486
|
-
formFieldParseErrors.set(
|
|
515
|
+
formFieldParseErrors.set(parser.errors);
|
|
487
516
|
}
|
|
517
|
+
const rawValue = linkedSignal(() => format(value()), ...(ngDevMode ? [{
|
|
518
|
+
debugName: "rawValue"
|
|
519
|
+
}] : []));
|
|
488
520
|
const result = rawValue;
|
|
521
|
+
result.parseErrors = parser.errors;
|
|
489
522
|
const originalSet = result.set.bind(result);
|
|
490
523
|
result.set = newRawValue => {
|
|
491
|
-
|
|
492
|
-
parseErrors.set(result.errors ?? []);
|
|
493
|
-
if (result.value !== undefined) {
|
|
494
|
-
value.set(result.value);
|
|
495
|
-
}
|
|
524
|
+
parser.setRawValue(newRawValue);
|
|
496
525
|
originalSet(newRawValue);
|
|
497
526
|
};
|
|
498
527
|
result.update = updateFn => {
|
|
499
528
|
result.set(updateFn(rawValue()));
|
|
500
529
|
};
|
|
501
|
-
result.parseErrors = parseErrors.asReadonly();
|
|
502
530
|
return result;
|
|
503
531
|
}
|
|
504
532
|
|
|
@@ -621,29 +649,46 @@ function isTextualFormElement(element) {
|
|
|
621
649
|
return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';
|
|
622
650
|
}
|
|
623
651
|
function getNativeControlValue(element, currentValue) {
|
|
652
|
+
let modelValue;
|
|
653
|
+
if (element.validity.badInput) {
|
|
654
|
+
return {
|
|
655
|
+
error: new NativeInputParseError()
|
|
656
|
+
};
|
|
657
|
+
}
|
|
624
658
|
switch (element.type) {
|
|
625
659
|
case 'checkbox':
|
|
626
|
-
return
|
|
660
|
+
return {
|
|
661
|
+
value: element.checked
|
|
662
|
+
};
|
|
627
663
|
case 'number':
|
|
628
664
|
case 'range':
|
|
629
665
|
case 'datetime-local':
|
|
630
|
-
|
|
631
|
-
|
|
666
|
+
modelValue = untracked(currentValue);
|
|
667
|
+
if (typeof modelValue === 'number' || modelValue === null) {
|
|
668
|
+
return {
|
|
669
|
+
value: element.value === '' ? null : element.valueAsNumber
|
|
670
|
+
};
|
|
632
671
|
}
|
|
633
672
|
break;
|
|
634
673
|
case 'date':
|
|
635
674
|
case 'month':
|
|
636
675
|
case 'time':
|
|
637
676
|
case 'week':
|
|
638
|
-
|
|
639
|
-
if (
|
|
640
|
-
return
|
|
641
|
-
|
|
642
|
-
|
|
677
|
+
modelValue = untracked(currentValue);
|
|
678
|
+
if (modelValue === null || modelValue instanceof Date) {
|
|
679
|
+
return {
|
|
680
|
+
value: element.valueAsDate
|
|
681
|
+
};
|
|
682
|
+
} else if (typeof modelValue === 'number') {
|
|
683
|
+
return {
|
|
684
|
+
value: element.valueAsNumber
|
|
685
|
+
};
|
|
643
686
|
}
|
|
644
687
|
break;
|
|
645
688
|
}
|
|
646
|
-
return
|
|
689
|
+
return {
|
|
690
|
+
value: element.value
|
|
691
|
+
};
|
|
647
692
|
}
|
|
648
693
|
function setNativeControlValue(element, value) {
|
|
649
694
|
switch (element.type) {
|
|
@@ -659,6 +704,9 @@ function setNativeControlValue(element, value) {
|
|
|
659
704
|
if (typeof value === 'number') {
|
|
660
705
|
setNativeNumberControlValue(element, value);
|
|
661
706
|
return;
|
|
707
|
+
} else if (value === null) {
|
|
708
|
+
element.value = '';
|
|
709
|
+
return;
|
|
662
710
|
}
|
|
663
711
|
break;
|
|
664
712
|
case 'date':
|
|
@@ -803,13 +851,12 @@ function isRelevantSelectMutation(mutation) {
|
|
|
803
851
|
return false;
|
|
804
852
|
}
|
|
805
853
|
|
|
806
|
-
function nativeControlCreate(host, parent) {
|
|
854
|
+
function nativeControlCreate(host, parent, parseErrorsSource) {
|
|
807
855
|
let updateMode = false;
|
|
808
856
|
const input = parent.nativeFormElement;
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
});
|
|
857
|
+
const parser = createParser(() => parent.state().value(), rawValue => parent.state().controlValue.set(rawValue), () => getNativeControlValue(input, parent.state().value));
|
|
858
|
+
parseErrorsSource.set(parser.errors);
|
|
859
|
+
host.listenToDom('input', () => parser.setRawValue(undefined));
|
|
813
860
|
host.listenToDom('blur', () => parent.state().markAsTouched());
|
|
814
861
|
parent.registerAsBinding();
|
|
815
862
|
if (input.tagName === 'SELECT') {
|
|
@@ -843,15 +890,15 @@ function nativeControlCreate(host, parent) {
|
|
|
843
890
|
const ɵNgFieldDirective = Symbol();
|
|
844
891
|
const FORM_FIELD = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FORM_FIELD' : '');
|
|
845
892
|
class FormField {
|
|
846
|
-
|
|
893
|
+
field = input.required({
|
|
847
894
|
...(ngDevMode ? {
|
|
848
|
-
debugName: "
|
|
895
|
+
debugName: "field"
|
|
849
896
|
} : {}),
|
|
850
897
|
alias: 'formField'
|
|
851
898
|
});
|
|
852
899
|
renderer = inject(Renderer2);
|
|
853
900
|
destroyRef = inject(DestroyRef);
|
|
854
|
-
state = computed(() => this.
|
|
901
|
+
state = computed(() => this.field()(), ...(ngDevMode ? [{
|
|
855
902
|
debugName: "state"
|
|
856
903
|
}] : []));
|
|
857
904
|
injector = inject(Injector);
|
|
@@ -877,7 +924,7 @@ class FormField {
|
|
|
877
924
|
}
|
|
878
925
|
parseErrors = computed(() => this.parseErrorsSource()?.().map(err => ({
|
|
879
926
|
...err,
|
|
880
|
-
fieldTree: untracked(this.fieldTree
|
|
927
|
+
fieldTree: untracked(this.state).fieldTree,
|
|
881
928
|
formField: this
|
|
882
929
|
})) ?? [], ...(ngDevMode ? [{
|
|
883
930
|
debugName: "parseErrors"
|
|
@@ -917,12 +964,12 @@ class FormField {
|
|
|
917
964
|
}
|
|
918
965
|
registerAsBinding(bindingOptions) {
|
|
919
966
|
if (this.isFieldBinding) {
|
|
920
|
-
throw new _RuntimeError(1913, ngDevMode && 'FormField already registered as a binding');
|
|
967
|
+
throw new _RuntimeError(1913, typeof ngDevMode !== 'undefined' && ngDevMode && 'FormField already registered as a binding');
|
|
921
968
|
}
|
|
922
969
|
this.isFieldBinding = true;
|
|
923
970
|
this.installClassBindingEffect();
|
|
924
971
|
if (bindingOptions?.focus) {
|
|
925
|
-
this.focuser = bindingOptions.focus;
|
|
972
|
+
this.focuser = focusOptions => bindingOptions.focus(focusOptions);
|
|
926
973
|
}
|
|
927
974
|
effect(onCleanup => {
|
|
928
975
|
const fieldNode = this.state();
|
|
@@ -933,6 +980,17 @@ class FormField {
|
|
|
933
980
|
}, {
|
|
934
981
|
injector: this.injector
|
|
935
982
|
});
|
|
983
|
+
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
|
984
|
+
effect(() => {
|
|
985
|
+
const fieldNode = this.state();
|
|
986
|
+
if (fieldNode.hidden()) {
|
|
987
|
+
const path = fieldNode.structure.pathKeys().join('.') || '<root>';
|
|
988
|
+
console.warn(_formatRuntimeError(1916, `Field '${path}' is hidden but is being rendered. ` + `Hidden fields should be removed from the DOM using @if.`));
|
|
989
|
+
}
|
|
990
|
+
}, {
|
|
991
|
+
injector: this.injector
|
|
992
|
+
});
|
|
993
|
+
}
|
|
936
994
|
}
|
|
937
995
|
[ɵNgFieldDirective];
|
|
938
996
|
ɵngControlCreate(host) {
|
|
@@ -944,9 +1002,9 @@ class FormField {
|
|
|
944
1002
|
} else if (host.customControl) {
|
|
945
1003
|
this.ɵngControlUpdate = customControlCreate(host, this);
|
|
946
1004
|
} else if (this.elementIsNativeFormElement) {
|
|
947
|
-
this.ɵngControlUpdate = nativeControlCreate(host, this);
|
|
1005
|
+
this.ɵngControlUpdate = nativeControlCreate(host, this, this.parseErrorsSource);
|
|
948
1006
|
} 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.`);
|
|
1007
|
+
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
1008
|
}
|
|
951
1009
|
}
|
|
952
1010
|
ɵngControlUpdate;
|
|
@@ -972,7 +1030,7 @@ class FormField {
|
|
|
972
1030
|
}
|
|
973
1031
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
974
1032
|
minVersion: "12.0.0",
|
|
975
|
-
version: "21.2.0
|
|
1033
|
+
version: "21.2.0",
|
|
976
1034
|
ngImport: i0,
|
|
977
1035
|
type: FormField,
|
|
978
1036
|
deps: [],
|
|
@@ -980,13 +1038,13 @@ class FormField {
|
|
|
980
1038
|
});
|
|
981
1039
|
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
982
1040
|
minVersion: "17.1.0",
|
|
983
|
-
version: "21.2.0
|
|
1041
|
+
version: "21.2.0",
|
|
984
1042
|
type: FormField,
|
|
985
1043
|
isStandalone: true,
|
|
986
1044
|
selector: "[formField]",
|
|
987
1045
|
inputs: {
|
|
988
|
-
|
|
989
|
-
classPropertyName: "
|
|
1046
|
+
field: {
|
|
1047
|
+
classPropertyName: "field",
|
|
990
1048
|
publicName: "formField",
|
|
991
1049
|
isSignal: true,
|
|
992
1050
|
isRequired: true,
|
|
@@ -1012,7 +1070,7 @@ class FormField {
|
|
|
1012
1070
|
}
|
|
1013
1071
|
i0.ɵɵngDeclareClassMetadata({
|
|
1014
1072
|
minVersion: "12.0.0",
|
|
1015
|
-
version: "21.2.0
|
|
1073
|
+
version: "21.2.0",
|
|
1016
1074
|
ngImport: i0,
|
|
1017
1075
|
type: FormField,
|
|
1018
1076
|
decorators: [{
|
|
@@ -1033,7 +1091,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
1033
1091
|
}]
|
|
1034
1092
|
}],
|
|
1035
1093
|
propDecorators: {
|
|
1036
|
-
|
|
1094
|
+
field: [{
|
|
1037
1095
|
type: i0.Input,
|
|
1038
1096
|
args: [{
|
|
1039
1097
|
isSignal: true,
|
|
@@ -1057,7 +1115,7 @@ class FormRoot {
|
|
|
1057
1115
|
}
|
|
1058
1116
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
1059
1117
|
minVersion: "12.0.0",
|
|
1060
|
-
version: "21.2.0
|
|
1118
|
+
version: "21.2.0",
|
|
1061
1119
|
ngImport: i0,
|
|
1062
1120
|
type: FormRoot,
|
|
1063
1121
|
deps: [],
|
|
@@ -1065,7 +1123,7 @@ class FormRoot {
|
|
|
1065
1123
|
});
|
|
1066
1124
|
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
1067
1125
|
minVersion: "17.1.0",
|
|
1068
|
-
version: "21.2.0
|
|
1126
|
+
version: "21.2.0",
|
|
1069
1127
|
type: FormRoot,
|
|
1070
1128
|
isStandalone: true,
|
|
1071
1129
|
selector: "form[formRoot]",
|
|
@@ -1091,7 +1149,7 @@ class FormRoot {
|
|
|
1091
1149
|
}
|
|
1092
1150
|
i0.ɵɵngDeclareClassMetadata({
|
|
1093
1151
|
minVersion: "12.0.0",
|
|
1094
|
-
version: "21.2.0
|
|
1152
|
+
version: "21.2.0",
|
|
1095
1153
|
ngImport: i0,
|
|
1096
1154
|
type: FormRoot,
|
|
1097
1155
|
decorators: [{
|
|
@@ -1116,5 +1174,5 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
1116
1174
|
}
|
|
1117
1175
|
});
|
|
1118
1176
|
|
|
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 };
|
|
1177
|
+
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
1178
|
//# sourceMappingURL=signals.mjs.map
|