@angular/forms 21.2.0-next.2 → 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/{_structure-chunk.mjs → _validation_errors-chunk.mjs} +86 -81
- package/fesm2022/_validation_errors-chunk.mjs.map +1 -0
- package/fesm2022/forms.mjs +170 -189
- package/fesm2022/forms.mjs.map +1 -1
- package/fesm2022/signals-compat.mjs +9 -11
- package/fesm2022/signals-compat.mjs.map +1 -1
- package/fesm2022/signals.mjs +1017 -858
- 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 +302 -262
- package/types/forms.d.ts +1 -1
- package/types/signals-compat.d.ts +1 -1
- package/types/signals.d.ts +119 -15
- package/fesm2022/_structure-chunk.mjs.map +0 -1
package/fesm2022/signals.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
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, ɵRuntimeError as _RuntimeError, untracked, input,
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
export { MetadataKey, MetadataReducer, apply, applyEach, applyWhen, applyWhenValue, form, schema, submit } from './_structure-chunk.mjs';
|
|
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
|
+
import { assertPathIsCurrent, FieldPathNode, addDefaultField, metadata, createMetadataKey, MAX, MAX_LENGTH, MIN, MIN_LENGTH, PATTERN, REQUIRED, createManagedMetadataKey, DEBOUNCER, signalErrorsToValidationErrors, submit } from './_validation_errors-chunk.mjs';
|
|
10
|
+
export { MetadataKey, MetadataReducer, apply, applyEach, applyWhen, applyWhenValue, form, schema } from './_validation_errors-chunk.mjs';
|
|
12
11
|
import { httpResource } from '@angular/common/http';
|
|
12
|
+
import { Validators, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
|
|
13
13
|
import '@angular/core/primitives/signals';
|
|
14
14
|
|
|
15
15
|
const SIGNAL_FORMS_CONFIG = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'SIGNAL_FORMS_CONFIG' : '');
|
|
@@ -21,989 +21,1148 @@ function provideSignalFormsConfig(config) {
|
|
|
21
21
|
}];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
get valid() {
|
|
34
|
-
return this.field().valid();
|
|
35
|
-
}
|
|
36
|
-
get invalid() {
|
|
37
|
-
return this.field().invalid();
|
|
38
|
-
}
|
|
39
|
-
get pending() {
|
|
40
|
-
return this.field().pending();
|
|
41
|
-
}
|
|
42
|
-
get disabled() {
|
|
43
|
-
return this.field().disabled();
|
|
44
|
-
}
|
|
45
|
-
get enabled() {
|
|
46
|
-
return !this.field().disabled();
|
|
47
|
-
}
|
|
48
|
-
get errors() {
|
|
49
|
-
return signalErrorsToValidationErrors(this.field().errors());
|
|
50
|
-
}
|
|
51
|
-
get pristine() {
|
|
52
|
-
return !this.field().dirty();
|
|
53
|
-
}
|
|
54
|
-
get dirty() {
|
|
55
|
-
return this.field().dirty();
|
|
56
|
-
}
|
|
57
|
-
get touched() {
|
|
58
|
-
return this.field().touched();
|
|
59
|
-
}
|
|
60
|
-
get untouched() {
|
|
61
|
-
return !this.field().touched();
|
|
62
|
-
}
|
|
63
|
-
get status() {
|
|
64
|
-
if (this.field().disabled()) {
|
|
65
|
-
return 'DISABLED';
|
|
66
|
-
}
|
|
67
|
-
if (this.field().valid()) {
|
|
68
|
-
return 'VALID';
|
|
69
|
-
}
|
|
70
|
-
if (this.field().invalid()) {
|
|
71
|
-
return 'INVALID';
|
|
72
|
-
}
|
|
73
|
-
if (this.field().pending()) {
|
|
74
|
-
return 'PENDING';
|
|
24
|
+
function disabled(path, logic) {
|
|
25
|
+
assertPathIsCurrent(path);
|
|
26
|
+
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
27
|
+
pathNode.builder.addDisabledReasonRule(ctx => {
|
|
28
|
+
let result = true;
|
|
29
|
+
if (typeof logic === 'string') {
|
|
30
|
+
result = logic;
|
|
31
|
+
} else if (logic) {
|
|
32
|
+
result = logic(ctx);
|
|
75
33
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return this.field().required();
|
|
34
|
+
if (typeof result === 'string') {
|
|
35
|
+
return {
|
|
36
|
+
fieldTree: ctx.fieldTree,
|
|
37
|
+
message: result
|
|
38
|
+
};
|
|
82
39
|
}
|
|
83
|
-
return
|
|
40
|
+
return result ? {
|
|
41
|
+
fieldTree: ctx.fieldTree
|
|
42
|
+
} : undefined;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hidden(path, logic) {
|
|
47
|
+
assertPathIsCurrent(path);
|
|
48
|
+
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
49
|
+
pathNode.builder.addHiddenRule(logic);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readonly(path, logic = () => true) {
|
|
53
|
+
assertPathIsCurrent(path);
|
|
54
|
+
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
55
|
+
pathNode.builder.addReadonlyRule(logic);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getLengthOrSize(value) {
|
|
59
|
+
const v = value;
|
|
60
|
+
return typeof v.length === 'number' ? v.length : v.size;
|
|
61
|
+
}
|
|
62
|
+
function getOption(opt, ctx) {
|
|
63
|
+
return opt instanceof Function ? opt(ctx) : opt;
|
|
64
|
+
}
|
|
65
|
+
function isEmpty(value) {
|
|
66
|
+
if (typeof value === 'number') {
|
|
67
|
+
return isNaN(value);
|
|
84
68
|
}
|
|
85
|
-
|
|
69
|
+
return value === '' || value === false || value == null;
|
|
86
70
|
}
|
|
87
71
|
|
|
88
|
-
function
|
|
89
|
-
|
|
72
|
+
function validate(path, logic) {
|
|
73
|
+
assertPathIsCurrent(path);
|
|
74
|
+
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
75
|
+
pathNode.builder.addSyncErrorRule(ctx => {
|
|
76
|
+
return addDefaultField(logic(ctx), ctx.fieldTree);
|
|
77
|
+
});
|
|
90
78
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
79
|
+
|
|
80
|
+
function requiredError(options) {
|
|
81
|
+
return new RequiredValidationError(options);
|
|
82
|
+
}
|
|
83
|
+
function minError(min, options) {
|
|
84
|
+
return new MinValidationError(min, options);
|
|
85
|
+
}
|
|
86
|
+
function maxError(max, options) {
|
|
87
|
+
return new MaxValidationError(max, options);
|
|
88
|
+
}
|
|
89
|
+
function minLengthError(minLength, options) {
|
|
90
|
+
return new MinLengthValidationError(minLength, options);
|
|
91
|
+
}
|
|
92
|
+
function maxLengthError(maxLength, options) {
|
|
93
|
+
return new MaxLengthValidationError(maxLength, options);
|
|
94
|
+
}
|
|
95
|
+
function patternError(pattern, options) {
|
|
96
|
+
return new PatternValidationError(pattern, options);
|
|
97
|
+
}
|
|
98
|
+
function emailError(options) {
|
|
99
|
+
return new EmailValidationError(options);
|
|
100
|
+
}
|
|
101
|
+
class BaseNgValidationError {
|
|
102
|
+
__brand = undefined;
|
|
103
|
+
kind = '';
|
|
104
|
+
fieldTree;
|
|
105
|
+
message;
|
|
106
|
+
constructor(options) {
|
|
107
|
+
if (options) {
|
|
108
|
+
Object.assign(this, options);
|
|
109
|
+
}
|
|
94
110
|
}
|
|
95
|
-
const type = element.type;
|
|
96
|
-
return type === 'date' || type === 'datetime-local' || type === 'month' || type === 'number' || type === 'range' || type === 'time' || type === 'week';
|
|
97
111
|
}
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
class RequiredValidationError extends BaseNgValidationError {
|
|
113
|
+
kind = 'required';
|
|
100
114
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
case 'datetime-local':
|
|
108
|
-
if (typeof untracked(currentValue) === 'number') {
|
|
109
|
-
return element.valueAsNumber;
|
|
110
|
-
}
|
|
111
|
-
break;
|
|
112
|
-
case 'date':
|
|
113
|
-
case 'month':
|
|
114
|
-
case 'time':
|
|
115
|
-
case 'week':
|
|
116
|
-
const value = untracked(currentValue);
|
|
117
|
-
if (value === null || value instanceof Date) {
|
|
118
|
-
return element.valueAsDate;
|
|
119
|
-
} else if (typeof value === 'number') {
|
|
120
|
-
return element.valueAsNumber;
|
|
121
|
-
}
|
|
122
|
-
break;
|
|
115
|
+
class MinValidationError extends BaseNgValidationError {
|
|
116
|
+
min;
|
|
117
|
+
kind = 'min';
|
|
118
|
+
constructor(min, options) {
|
|
119
|
+
super(options);
|
|
120
|
+
this.min = min;
|
|
123
121
|
}
|
|
124
|
-
return element.value;
|
|
125
122
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
element.checked = value === element.value;
|
|
133
|
-
return;
|
|
134
|
-
case 'number':
|
|
135
|
-
case 'range':
|
|
136
|
-
case 'datetime-local':
|
|
137
|
-
if (typeof value === 'number') {
|
|
138
|
-
setNativeNumberControlValue(element, value);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
break;
|
|
142
|
-
case 'date':
|
|
143
|
-
case 'month':
|
|
144
|
-
case 'time':
|
|
145
|
-
case 'week':
|
|
146
|
-
if (value === null || value instanceof Date) {
|
|
147
|
-
element.valueAsDate = value;
|
|
148
|
-
return;
|
|
149
|
-
} else if (typeof value === 'number') {
|
|
150
|
-
setNativeNumberControlValue(element, value);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
123
|
+
class MaxValidationError extends BaseNgValidationError {
|
|
124
|
+
max;
|
|
125
|
+
kind = 'max';
|
|
126
|
+
constructor(max, options) {
|
|
127
|
+
super(options);
|
|
128
|
+
this.max = max;
|
|
153
129
|
}
|
|
154
|
-
element.value = value;
|
|
155
130
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
131
|
+
class MinLengthValidationError extends BaseNgValidationError {
|
|
132
|
+
minLength;
|
|
133
|
+
kind = 'minLength';
|
|
134
|
+
constructor(minLength, options) {
|
|
135
|
+
super(options);
|
|
136
|
+
this.minLength = minLength;
|
|
161
137
|
}
|
|
162
138
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
case 'readonly':
|
|
170
|
-
case 'required':
|
|
171
|
-
if (value) {
|
|
172
|
-
renderer.setAttribute(element, name, '');
|
|
173
|
-
} else {
|
|
174
|
-
renderer.removeAttribute(element, name);
|
|
175
|
-
}
|
|
176
|
-
break;
|
|
177
|
-
case 'max':
|
|
178
|
-
case 'min':
|
|
179
|
-
case 'minLength':
|
|
180
|
-
case 'maxLength':
|
|
181
|
-
if (value !== undefined) {
|
|
182
|
-
renderer.setAttribute(element, name, value.toString());
|
|
183
|
-
} else {
|
|
184
|
-
renderer.removeAttribute(element, name);
|
|
185
|
-
}
|
|
186
|
-
break;
|
|
139
|
+
class MaxLengthValidationError extends BaseNgValidationError {
|
|
140
|
+
maxLength;
|
|
141
|
+
kind = 'maxLength';
|
|
142
|
+
constructor(maxLength, options) {
|
|
143
|
+
super(options);
|
|
144
|
+
this.maxLength = maxLength;
|
|
187
145
|
}
|
|
188
146
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
hidden: 'hidden',
|
|
196
|
-
invalid: 'invalid',
|
|
197
|
-
max: 'max',
|
|
198
|
-
maxLength: 'maxLength',
|
|
199
|
-
min: 'min',
|
|
200
|
-
minLength: 'minLength',
|
|
201
|
-
name: 'name',
|
|
202
|
-
pattern: 'pattern',
|
|
203
|
-
pending: 'pending',
|
|
204
|
-
readonly: 'readonly',
|
|
205
|
-
required: 'required',
|
|
206
|
-
touched: 'touched'
|
|
207
|
-
};
|
|
208
|
-
const CONTROL_BINDING_TO_FIELD_STATE_KEY = /* @__PURE__ */(() => {
|
|
209
|
-
const map = {};
|
|
210
|
-
for (const key of Object.keys(FIELD_STATE_KEY_TO_CONTROL_BINDING)) {
|
|
211
|
-
map[FIELD_STATE_KEY_TO_CONTROL_BINDING[key]] = key;
|
|
147
|
+
class PatternValidationError extends BaseNgValidationError {
|
|
148
|
+
pattern;
|
|
149
|
+
kind = 'pattern';
|
|
150
|
+
constructor(pattern, options) {
|
|
151
|
+
super(options);
|
|
152
|
+
this.pattern = pattern;
|
|
212
153
|
}
|
|
213
|
-
return map;
|
|
214
|
-
})();
|
|
215
|
-
function readFieldStateBindingValue(fieldState, key) {
|
|
216
|
-
const property = CONTROL_BINDING_TO_FIELD_STATE_KEY[key];
|
|
217
|
-
return fieldState[property]?.();
|
|
218
154
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return {};
|
|
155
|
+
class EmailValidationError extends BaseNgValidationError {
|
|
156
|
+
kind = 'email';
|
|
222
157
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
bindings[key] = value;
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
return false;
|
|
158
|
+
class NativeInputParseError extends BaseNgValidationError {
|
|
159
|
+
kind = 'parse';
|
|
229
160
|
}
|
|
161
|
+
const NgValidationError = BaseNgValidationError;
|
|
230
162
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return () => {
|
|
237
|
-
const fieldState = parent.state();
|
|
238
|
-
const value = fieldState.value();
|
|
239
|
-
if (bindingUpdated(bindings, 'controlValue', value)) {
|
|
240
|
-
untracked(() => parent.controlValueAccessor.writeValue(value));
|
|
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])?)*$/;
|
|
164
|
+
function email(path, config) {
|
|
165
|
+
validate(path, ctx => {
|
|
166
|
+
if (isEmpty(ctx.value())) {
|
|
167
|
+
return undefined;
|
|
241
168
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
setNativeDomProperty(parent.renderer, parent.nativeFormElement, name, value);
|
|
250
|
-
}
|
|
169
|
+
if (!EMAIL_REGEXP.test(ctx.value())) {
|
|
170
|
+
if (config?.error) {
|
|
171
|
+
return getOption(config.error, ctx);
|
|
172
|
+
} else {
|
|
173
|
+
return emailError({
|
|
174
|
+
message: getOption(config?.message, ctx)
|
|
175
|
+
});
|
|
251
176
|
}
|
|
252
177
|
}
|
|
253
|
-
|
|
178
|
+
return undefined;
|
|
179
|
+
});
|
|
254
180
|
}
|
|
255
181
|
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (bindingUpdated(bindings, 'controlValue', controlValue)) {
|
|
265
|
-
host.setCustomControlModelInput(controlValue);
|
|
182
|
+
function max(path, maxValue, config) {
|
|
183
|
+
const MAX_MEMO = metadata(path, createMetadataKey(), ctx => typeof maxValue === 'number' ? maxValue : maxValue(ctx));
|
|
184
|
+
metadata(path, MAX, ({
|
|
185
|
+
state
|
|
186
|
+
}) => state.metadata(MAX_MEMO)());
|
|
187
|
+
validate(path, ctx => {
|
|
188
|
+
if (isEmpty(ctx.value())) {
|
|
189
|
+
return undefined;
|
|
266
190
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
191
|
+
const max = ctx.state.metadata(MAX_MEMO)();
|
|
192
|
+
if (max === undefined || Number.isNaN(max)) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
const value = ctx.value();
|
|
196
|
+
const numValue = !value && value !== 0 ? NaN : Number(value);
|
|
197
|
+
if (numValue > max) {
|
|
198
|
+
if (config?.error) {
|
|
199
|
+
return getOption(config.error, ctx);
|
|
271
200
|
} else {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
host.setInputOnDirectives(name, value);
|
|
276
|
-
if (parent.elementAcceptsNativeProperty(name) && !host.customControlHasInput(name)) {
|
|
277
|
-
setNativeDomProperty(parent.renderer, parent.nativeFormElement, name, value);
|
|
278
|
-
}
|
|
201
|
+
return maxError(max, {
|
|
202
|
+
message: getOption(config?.message, ctx)
|
|
203
|
+
});
|
|
279
204
|
}
|
|
280
205
|
}
|
|
281
|
-
|
|
206
|
+
return undefined;
|
|
207
|
+
});
|
|
282
208
|
}
|
|
283
209
|
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
210
|
+
function maxLength(path, maxLength, config) {
|
|
211
|
+
const MAX_LENGTH_MEMO = metadata(path, createMetadataKey(), ctx => typeof maxLength === 'number' ? maxLength : maxLength(ctx));
|
|
212
|
+
metadata(path, MAX_LENGTH, ({
|
|
213
|
+
state
|
|
214
|
+
}) => state.metadata(MAX_LENGTH_MEMO)());
|
|
215
|
+
validate(path, ctx => {
|
|
216
|
+
if (isEmpty(ctx.value())) {
|
|
217
|
+
return undefined;
|
|
291
218
|
}
|
|
219
|
+
const maxLength = ctx.state.metadata(MAX_LENGTH_MEMO)();
|
|
220
|
+
if (maxLength === undefined) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
if (getLengthOrSize(ctx.value()) > maxLength) {
|
|
224
|
+
if (config?.error) {
|
|
225
|
+
return getOption(config.error, ctx);
|
|
226
|
+
} else {
|
|
227
|
+
return maxLengthError(maxLength, {
|
|
228
|
+
message: getOption(config?.message, ctx)
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
292
233
|
});
|
|
293
|
-
observer.observe(select, {
|
|
294
|
-
attributes: true,
|
|
295
|
-
attributeFilter: ['value'],
|
|
296
|
-
characterData: true,
|
|
297
|
-
childList: true,
|
|
298
|
-
subtree: true
|
|
299
|
-
});
|
|
300
|
-
destroyRef.onDestroy(() => observer.disconnect());
|
|
301
234
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
235
|
+
|
|
236
|
+
function min(path, minValue, config) {
|
|
237
|
+
const MIN_MEMO = metadata(path, createMetadataKey(), ctx => typeof minValue === 'number' ? minValue : minValue(ctx));
|
|
238
|
+
metadata(path, MIN, ({
|
|
239
|
+
state
|
|
240
|
+
}) => state.metadata(MIN_MEMO)());
|
|
241
|
+
validate(path, ctx => {
|
|
242
|
+
if (isEmpty(ctx.value())) {
|
|
243
|
+
return undefined;
|
|
306
244
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
245
|
+
const min = ctx.state.metadata(MIN_MEMO)();
|
|
246
|
+
if (min === undefined || Number.isNaN(min)) {
|
|
247
|
+
return undefined;
|
|
311
248
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
249
|
+
const value = ctx.value();
|
|
250
|
+
const numValue = !value && value !== 0 ? NaN : Number(value);
|
|
251
|
+
if (numValue < min) {
|
|
252
|
+
if (config?.error) {
|
|
253
|
+
return getOption(config.error, ctx);
|
|
254
|
+
} else {
|
|
255
|
+
return minError(min, {
|
|
256
|
+
message: getOption(config?.message, ctx)
|
|
257
|
+
});
|
|
315
258
|
}
|
|
316
259
|
}
|
|
317
|
-
return
|
|
318
|
-
}
|
|
319
|
-
if (mutation.type === 'attributes' && mutation.target instanceof HTMLOptionElement) {
|
|
320
|
-
return true;
|
|
321
|
-
}
|
|
322
|
-
return false;
|
|
260
|
+
return undefined;
|
|
261
|
+
});
|
|
323
262
|
}
|
|
324
263
|
|
|
325
|
-
function
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
parent.registerAsBinding();
|
|
334
|
-
if (input.tagName === 'SELECT') {
|
|
335
|
-
observeSelectMutations(input, () => {
|
|
336
|
-
if (!updateMode) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
input.value = parent.state().controlValue();
|
|
340
|
-
}, parent.destroyRef);
|
|
341
|
-
}
|
|
342
|
-
const bindings = createBindings();
|
|
343
|
-
return () => {
|
|
344
|
-
const state = parent.state();
|
|
345
|
-
const controlValue = state.controlValue();
|
|
346
|
-
if (bindingUpdated(bindings, 'controlValue', controlValue)) {
|
|
347
|
-
setNativeControlValue(input, controlValue);
|
|
264
|
+
function minLength(path, minLength, config) {
|
|
265
|
+
const MIN_LENGTH_MEMO = metadata(path, createMetadataKey(), ctx => typeof minLength === 'number' ? minLength : minLength(ctx));
|
|
266
|
+
metadata(path, MIN_LENGTH, ({
|
|
267
|
+
state
|
|
268
|
+
}) => state.metadata(MIN_LENGTH_MEMO)());
|
|
269
|
+
validate(path, ctx => {
|
|
270
|
+
if (isEmpty(ctx.value())) {
|
|
271
|
+
return undefined;
|
|
348
272
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
273
|
+
const minLength = ctx.state.metadata(MIN_LENGTH_MEMO)();
|
|
274
|
+
if (minLength === undefined) {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
if (getLengthOrSize(ctx.value()) < minLength) {
|
|
278
|
+
if (config?.error) {
|
|
279
|
+
return getOption(config.error, ctx);
|
|
280
|
+
} else {
|
|
281
|
+
return minLengthError(minLength, {
|
|
282
|
+
message: getOption(config?.message, ctx)
|
|
283
|
+
});
|
|
356
284
|
}
|
|
357
285
|
}
|
|
358
|
-
|
|
359
|
-
};
|
|
286
|
+
return undefined;
|
|
287
|
+
});
|
|
360
288
|
}
|
|
361
289
|
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
});
|
|
371
|
-
renderer = inject(Renderer2);
|
|
372
|
-
destroyRef = inject(DestroyRef);
|
|
373
|
-
state = computed(() => this.fieldTree()(), ...(ngDevMode ? [{
|
|
374
|
-
debugName: "state"
|
|
375
|
-
}] : []));
|
|
376
|
-
injector = inject(Injector);
|
|
377
|
-
element = inject(ElementRef).nativeElement;
|
|
378
|
-
elementIsNativeFormElement = isNativeFormElement(this.element);
|
|
379
|
-
elementAcceptsNumericValues = isNumericFormElement(this.element);
|
|
380
|
-
elementAcceptsTextualValues = isTextualFormElement(this.element);
|
|
381
|
-
nativeFormElement = this.elementIsNativeFormElement ? this.element : undefined;
|
|
382
|
-
focuser = options => this.element.focus(options);
|
|
383
|
-
controlValueAccessors = inject(NG_VALUE_ACCESSOR, {
|
|
384
|
-
optional: true,
|
|
385
|
-
self: true
|
|
386
|
-
});
|
|
387
|
-
config = inject(SIGNAL_FORMS_CONFIG, {
|
|
388
|
-
optional: true
|
|
389
|
-
});
|
|
390
|
-
parseErrorsSource = signal(undefined, ...(ngDevMode ? [{
|
|
391
|
-
debugName: "parseErrorsSource"
|
|
392
|
-
}] : []));
|
|
393
|
-
_interopNgControl;
|
|
394
|
-
get interopNgControl() {
|
|
395
|
-
return this._interopNgControl ??= new InteropNgControl(this.state);
|
|
396
|
-
}
|
|
397
|
-
parseErrors = computed(() => this.parseErrorsSource()?.().map(err => ({
|
|
398
|
-
...err,
|
|
399
|
-
fieldTree: untracked(this.fieldTree),
|
|
400
|
-
formField: this
|
|
401
|
-
})) ?? [], ...(ngDevMode ? [{
|
|
402
|
-
debugName: "parseErrors"
|
|
403
|
-
}] : []));
|
|
404
|
-
errors = computed(() => this.state().errors().filter(err => !err.formField || err.formField === this), ...(ngDevMode ? [{
|
|
405
|
-
debugName: "errors"
|
|
406
|
-
}] : []));
|
|
407
|
-
isFieldBinding = false;
|
|
408
|
-
get controlValueAccessor() {
|
|
409
|
-
return this.controlValueAccessors?.[0] ?? this.interopNgControl?.valueAccessor ?? undefined;
|
|
410
|
-
}
|
|
411
|
-
installClassBindingEffect() {
|
|
412
|
-
const classes = Object.entries(this.config?.classes ?? {}).map(([className, computation]) => [className, computed(() => computation(this))]);
|
|
413
|
-
if (classes.length === 0) {
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
const bindings = createBindings();
|
|
417
|
-
afterRenderEffect({
|
|
418
|
-
write: () => {
|
|
419
|
-
for (const [className, computation] of classes) {
|
|
420
|
-
const active = computation();
|
|
421
|
-
if (bindingUpdated(bindings, className, active)) {
|
|
422
|
-
if (active) {
|
|
423
|
-
this.renderer.addClass(this.element, className);
|
|
424
|
-
} else {
|
|
425
|
-
this.renderer.removeClass(this.element, className);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}, {
|
|
431
|
-
injector: this.injector
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
focus(options) {
|
|
435
|
-
this.focuser(options);
|
|
436
|
-
}
|
|
437
|
-
registerAsBinding(bindingOptions) {
|
|
438
|
-
if (this.isFieldBinding) {
|
|
439
|
-
throw new _RuntimeError(1913, ngDevMode && 'FormField already registered as a binding');
|
|
440
|
-
}
|
|
441
|
-
this.isFieldBinding = true;
|
|
442
|
-
this.installClassBindingEffect();
|
|
443
|
-
if (bindingOptions?.focus) {
|
|
444
|
-
this.focuser = bindingOptions.focus;
|
|
445
|
-
}
|
|
446
|
-
if (bindingOptions?.parseErrors) {
|
|
447
|
-
this.parseErrorsSource.set(bindingOptions.parseErrors);
|
|
448
|
-
}
|
|
449
|
-
effect(onCleanup => {
|
|
450
|
-
const fieldNode = this.state();
|
|
451
|
-
fieldNode.nodeState.formFieldBindings.update(controls => [...controls, this]);
|
|
452
|
-
onCleanup(() => {
|
|
453
|
-
fieldNode.nodeState.formFieldBindings.update(controls => controls.filter(c => c !== this));
|
|
454
|
-
});
|
|
455
|
-
}, {
|
|
456
|
-
injector: this.injector
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
[ɵNgFieldDirective];
|
|
460
|
-
ɵngControlCreate(host) {
|
|
461
|
-
if (host.hasPassThrough) {
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
if (this.controlValueAccessor) {
|
|
465
|
-
this.ɵngControlUpdate = cvaControlCreate(host, this);
|
|
466
|
-
} else if (host.customControl) {
|
|
467
|
-
this.ɵngControlUpdate = customControlCreate(host, this);
|
|
468
|
-
} else if (this.elementIsNativeFormElement) {
|
|
469
|
-
this.ɵngControlUpdate = nativeControlCreate(host, this);
|
|
470
|
-
} else {
|
|
471
|
-
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.`);
|
|
290
|
+
function pattern(path, pattern, config) {
|
|
291
|
+
const PATTERN_MEMO = metadata(path, createMetadataKey(), ctx => pattern instanceof RegExp ? pattern : pattern(ctx));
|
|
292
|
+
metadata(path, PATTERN, ({
|
|
293
|
+
state
|
|
294
|
+
}) => state.metadata(PATTERN_MEMO)());
|
|
295
|
+
validate(path, ctx => {
|
|
296
|
+
if (isEmpty(ctx.value())) {
|
|
297
|
+
return undefined;
|
|
472
298
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (!this.elementIsNativeFormElement) {
|
|
477
|
-
return false;
|
|
299
|
+
const pattern = ctx.state.metadata(PATTERN_MEMO)();
|
|
300
|
+
if (pattern === undefined) {
|
|
301
|
+
return undefined;
|
|
478
302
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
case 'required':
|
|
488
|
-
case 'readonly':
|
|
489
|
-
case 'name':
|
|
490
|
-
return true;
|
|
491
|
-
default:
|
|
492
|
-
return false;
|
|
303
|
+
if (!pattern.test(ctx.value())) {
|
|
304
|
+
if (config?.error) {
|
|
305
|
+
return getOption(config.error, ctx);
|
|
306
|
+
} else {
|
|
307
|
+
return patternError(pattern, {
|
|
308
|
+
message: getOption(config?.message, ctx)
|
|
309
|
+
});
|
|
310
|
+
}
|
|
493
311
|
}
|
|
494
|
-
|
|
495
|
-
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
496
|
-
minVersion: "12.0.0",
|
|
497
|
-
version: "21.2.0-next.2",
|
|
498
|
-
ngImport: i0,
|
|
499
|
-
type: FormField,
|
|
500
|
-
deps: [],
|
|
501
|
-
target: i0.ɵɵFactoryTarget.Directive
|
|
312
|
+
return undefined;
|
|
502
313
|
});
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function required(path, config) {
|
|
317
|
+
const REQUIRED_MEMO = metadata(path, createMetadataKey(), ctx => config?.when ? config.when(ctx) : true);
|
|
318
|
+
metadata(path, REQUIRED, ({
|
|
319
|
+
state
|
|
320
|
+
}) => state.metadata(REQUIRED_MEMO)());
|
|
321
|
+
validate(path, ctx => {
|
|
322
|
+
if (ctx.state.metadata(REQUIRED_MEMO)() && isEmpty(ctx.value())) {
|
|
323
|
+
if (config?.error) {
|
|
324
|
+
return getOption(config.error, ctx);
|
|
325
|
+
} else {
|
|
326
|
+
return requiredError({
|
|
327
|
+
message: getOption(config?.message, ctx)
|
|
328
|
+
});
|
|
516
329
|
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
provide: FORM_FIELD,
|
|
520
|
-
useExisting: FormField
|
|
521
|
-
}, {
|
|
522
|
-
provide: NgControl,
|
|
523
|
-
useFactory: () => inject(FormField).interopNgControl
|
|
524
|
-
}],
|
|
525
|
-
exportAs: ["formField"],
|
|
526
|
-
controlCreate: {
|
|
527
|
-
passThroughInput: "formField"
|
|
528
|
-
},
|
|
529
|
-
ngImport: i0
|
|
330
|
+
}
|
|
331
|
+
return undefined;
|
|
530
332
|
});
|
|
531
333
|
}
|
|
532
|
-
i0.ɵɵngDeclareClassMetadata({
|
|
533
|
-
minVersion: "12.0.0",
|
|
534
|
-
version: "21.2.0-next.2",
|
|
535
|
-
ngImport: i0,
|
|
536
|
-
type: FormField,
|
|
537
|
-
decorators: [{
|
|
538
|
-
type: Directive,
|
|
539
|
-
args: [{
|
|
540
|
-
selector: '[formField]',
|
|
541
|
-
exportAs: 'formField',
|
|
542
|
-
providers: [{
|
|
543
|
-
provide: FORM_FIELD,
|
|
544
|
-
useExisting: FormField
|
|
545
|
-
}, {
|
|
546
|
-
provide: NgControl,
|
|
547
|
-
useFactory: () => inject(FormField).interopNgControl
|
|
548
|
-
}]
|
|
549
|
-
}]
|
|
550
|
-
}],
|
|
551
|
-
propDecorators: {
|
|
552
|
-
fieldTree: [{
|
|
553
|
-
type: i0.Input,
|
|
554
|
-
args: [{
|
|
555
|
-
isSignal: true,
|
|
556
|
-
alias: "formField",
|
|
557
|
-
required: true
|
|
558
|
-
}]
|
|
559
|
-
}]
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
334
|
|
|
563
|
-
function
|
|
335
|
+
function validateAsync(path, opts) {
|
|
564
336
|
assertPathIsCurrent(path);
|
|
565
337
|
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
338
|
+
const RESOURCE = createManagedMetadataKey(opts.factory);
|
|
339
|
+
metadata(path, RESOURCE, ctx => {
|
|
340
|
+
const node = ctx.stateOf(path);
|
|
341
|
+
const validationState = node.validationState;
|
|
342
|
+
if (validationState.shouldSkipValidation() || !validationState.syncValid()) {
|
|
343
|
+
return undefined;
|
|
572
344
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
345
|
+
return opts.params(ctx);
|
|
346
|
+
});
|
|
347
|
+
pathNode.builder.addAsyncErrorRule(ctx => {
|
|
348
|
+
const res = ctx.state.metadata(RESOURCE);
|
|
349
|
+
let errors;
|
|
350
|
+
switch (res.status()) {
|
|
351
|
+
case 'idle':
|
|
352
|
+
return undefined;
|
|
353
|
+
case 'loading':
|
|
354
|
+
case 'reloading':
|
|
355
|
+
return 'pending';
|
|
356
|
+
case 'resolved':
|
|
357
|
+
case 'local':
|
|
358
|
+
if (!res.hasValue()) {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
errors = opts.onSuccess(res.value(), ctx);
|
|
362
|
+
return addDefaultField(errors, ctx.fieldTree);
|
|
363
|
+
case 'error':
|
|
364
|
+
errors = opts.onError(res.error(), ctx);
|
|
365
|
+
return addDefaultField(errors, ctx.fieldTree);
|
|
578
366
|
}
|
|
579
|
-
return result ? {
|
|
580
|
-
fieldTree: ctx.fieldTree
|
|
581
|
-
} : undefined;
|
|
582
367
|
});
|
|
583
368
|
}
|
|
584
369
|
|
|
585
|
-
function
|
|
370
|
+
function validateTree(path, logic) {
|
|
586
371
|
assertPathIsCurrent(path);
|
|
587
372
|
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
588
|
-
pathNode.builder.
|
|
373
|
+
pathNode.builder.addSyncTreeErrorRule(ctx => addDefaultField(logic(ctx), ctx.fieldTree));
|
|
589
374
|
}
|
|
590
375
|
|
|
591
|
-
function
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
376
|
+
function validateStandardSchema(path, schema) {
|
|
377
|
+
const VALIDATOR_MEMO = metadata(path, createMetadataKey(), ctx => {
|
|
378
|
+
const resolvedSchema = typeof schema === 'function' ? schema(ctx) : schema;
|
|
379
|
+
return resolvedSchema ? resolvedSchema['~standard'].validate(ctx.value()) : undefined;
|
|
380
|
+
});
|
|
381
|
+
validateTree(path, ({
|
|
382
|
+
state,
|
|
383
|
+
fieldTreeOf
|
|
384
|
+
}) => {
|
|
385
|
+
const result = state.metadata(VALIDATOR_MEMO)();
|
|
386
|
+
if (!result || _isPromise(result)) {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
return result?.issues?.map(issue => standardIssueToFormTreeError(fieldTreeOf(path), issue)) ?? [];
|
|
390
|
+
});
|
|
391
|
+
validateAsync(path, {
|
|
392
|
+
params: ({
|
|
393
|
+
state
|
|
394
|
+
}) => {
|
|
395
|
+
const result = state.metadata(VALIDATOR_MEMO)();
|
|
396
|
+
return result && _isPromise(result) ? result : undefined;
|
|
397
|
+
},
|
|
398
|
+
factory: params => {
|
|
399
|
+
return resource({
|
|
400
|
+
params,
|
|
401
|
+
loader: async ({
|
|
402
|
+
params
|
|
403
|
+
}) => (await params)?.issues ?? []
|
|
404
|
+
});
|
|
405
|
+
},
|
|
406
|
+
onSuccess: (issues, {
|
|
407
|
+
fieldTreeOf
|
|
408
|
+
}) => {
|
|
409
|
+
return issues.map(issue => standardIssueToFormTreeError(fieldTreeOf(path), issue));
|
|
410
|
+
},
|
|
411
|
+
onError: () => {}
|
|
412
|
+
});
|
|
595
413
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const v = value;
|
|
599
|
-
return typeof v.length === 'number' ? v.length : v.size;
|
|
414
|
+
function standardSchemaError(issue, options) {
|
|
415
|
+
return new StandardSchemaValidationError(issue, options);
|
|
600
416
|
}
|
|
601
|
-
function
|
|
602
|
-
|
|
417
|
+
function standardIssueToFormTreeError(fieldTree, issue) {
|
|
418
|
+
let target = fieldTree;
|
|
419
|
+
for (const pathPart of issue.path ?? []) {
|
|
420
|
+
const pathKey = typeof pathPart === 'object' ? pathPart.key : pathPart;
|
|
421
|
+
target = target[pathKey];
|
|
422
|
+
}
|
|
423
|
+
return addDefaultField(standardSchemaError(issue, {
|
|
424
|
+
message: issue.message
|
|
425
|
+
}), target);
|
|
603
426
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
427
|
+
class StandardSchemaValidationError extends BaseNgValidationError {
|
|
428
|
+
issue;
|
|
429
|
+
kind = 'standardSchema';
|
|
430
|
+
constructor(issue, options) {
|
|
431
|
+
super(options);
|
|
432
|
+
this.issue = issue;
|
|
607
433
|
}
|
|
608
|
-
return value === '' || value === false || value == null;
|
|
609
434
|
}
|
|
610
435
|
|
|
611
|
-
function
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
436
|
+
function validateHttp(path, opts) {
|
|
437
|
+
validateAsync(path, {
|
|
438
|
+
params: opts.request,
|
|
439
|
+
factory: request => httpResource(request, opts.options),
|
|
440
|
+
onSuccess: opts.onSuccess,
|
|
441
|
+
onError: opts.onError
|
|
616
442
|
});
|
|
617
443
|
}
|
|
618
444
|
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
function
|
|
623
|
-
|
|
624
|
-
}
|
|
625
|
-
function maxError(max, options) {
|
|
626
|
-
return new MaxValidationError(max, options);
|
|
627
|
-
}
|
|
628
|
-
function minLengthError(minLength, options) {
|
|
629
|
-
return new MinLengthValidationError(minLength, options);
|
|
630
|
-
}
|
|
631
|
-
function maxLengthError(maxLength, options) {
|
|
632
|
-
return new MaxLengthValidationError(maxLength, options);
|
|
633
|
-
}
|
|
634
|
-
function patternError(pattern, options) {
|
|
635
|
-
return new PatternValidationError(pattern, options);
|
|
445
|
+
function debounce(path, durationOrDebouncer) {
|
|
446
|
+
assertPathIsCurrent(path);
|
|
447
|
+
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
448
|
+
const debouncer = typeof durationOrDebouncer === 'function' ? durationOrDebouncer : durationOrDebouncer > 0 ? debounceForDuration(durationOrDebouncer) : immediate;
|
|
449
|
+
pathNode.builder.addMetadataRule(DEBOUNCER, () => debouncer);
|
|
636
450
|
}
|
|
637
|
-
function
|
|
638
|
-
return
|
|
451
|
+
function debounceForDuration(durationInMilliseconds) {
|
|
452
|
+
return (_context, abortSignal) => {
|
|
453
|
+
return new Promise(resolve => {
|
|
454
|
+
let timeoutId;
|
|
455
|
+
const onAbort = () => {
|
|
456
|
+
clearTimeout(timeoutId);
|
|
457
|
+
resolve();
|
|
458
|
+
};
|
|
459
|
+
timeoutId = setTimeout(() => {
|
|
460
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
461
|
+
resolve();
|
|
462
|
+
}, durationInMilliseconds);
|
|
463
|
+
abortSignal.addEventListener('abort', onAbort, {
|
|
464
|
+
once: true
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
};
|
|
639
468
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
469
|
+
function immediate() {}
|
|
470
|
+
|
|
471
|
+
const FORM_FIELD_PARSE_ERRORS = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FORM_FIELD_PARSE_ERRORS' : '');
|
|
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);
|
|
648
486
|
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
487
|
+
};
|
|
488
|
+
return {
|
|
489
|
+
errors: errors.asReadonly(),
|
|
490
|
+
setRawValue
|
|
491
|
+
};
|
|
653
492
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
493
|
+
|
|
494
|
+
function transformedValue(value, options) {
|
|
495
|
+
const {
|
|
496
|
+
parse,
|
|
497
|
+
format
|
|
498
|
+
} = options;
|
|
499
|
+
const parser = createParser(value, value.set, parse);
|
|
500
|
+
const formFieldParseErrors = inject(FORM_FIELD_PARSE_ERRORS, {
|
|
501
|
+
self: true,
|
|
502
|
+
optional: true
|
|
503
|
+
});
|
|
504
|
+
if (formFieldParseErrors) {
|
|
505
|
+
formFieldParseErrors.set(parser.errors);
|
|
660
506
|
}
|
|
507
|
+
const rawValue = linkedSignal(() => format(value()), ...(ngDevMode ? [{
|
|
508
|
+
debugName: "rawValue"
|
|
509
|
+
}] : []));
|
|
510
|
+
const result = rawValue;
|
|
511
|
+
result.parseErrors = parser.errors;
|
|
512
|
+
const originalSet = result.set.bind(result);
|
|
513
|
+
result.set = newRawValue => {
|
|
514
|
+
parser.setRawValue(newRawValue);
|
|
515
|
+
originalSet(newRawValue);
|
|
516
|
+
};
|
|
517
|
+
result.update = updateFn => {
|
|
518
|
+
result.set(updateFn(rawValue()));
|
|
519
|
+
};
|
|
520
|
+
return result;
|
|
661
521
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
constructor(
|
|
666
|
-
|
|
667
|
-
this.max = max;
|
|
522
|
+
|
|
523
|
+
class InteropNgControl {
|
|
524
|
+
field;
|
|
525
|
+
constructor(field) {
|
|
526
|
+
this.field = field;
|
|
668
527
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
kind = 'minLength';
|
|
673
|
-
constructor(minLength, options) {
|
|
674
|
-
super(options);
|
|
675
|
-
this.minLength = minLength;
|
|
528
|
+
control = this;
|
|
529
|
+
get value() {
|
|
530
|
+
return this.field().value();
|
|
676
531
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
maxLength;
|
|
680
|
-
kind = 'maxLength';
|
|
681
|
-
constructor(maxLength, options) {
|
|
682
|
-
super(options);
|
|
683
|
-
this.maxLength = maxLength;
|
|
532
|
+
get valid() {
|
|
533
|
+
return this.field().valid();
|
|
684
534
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
pattern;
|
|
688
|
-
kind = 'pattern';
|
|
689
|
-
constructor(pattern, options) {
|
|
690
|
-
super(options);
|
|
691
|
-
this.pattern = pattern;
|
|
535
|
+
get invalid() {
|
|
536
|
+
return this.field().invalid();
|
|
692
537
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
538
|
+
get pending() {
|
|
539
|
+
return this.field().pending();
|
|
540
|
+
}
|
|
541
|
+
get disabled() {
|
|
542
|
+
return this.field().disabled();
|
|
543
|
+
}
|
|
544
|
+
get enabled() {
|
|
545
|
+
return !this.field().disabled();
|
|
546
|
+
}
|
|
547
|
+
get errors() {
|
|
548
|
+
return signalErrorsToValidationErrors(this.field().errors());
|
|
549
|
+
}
|
|
550
|
+
get pristine() {
|
|
551
|
+
return !this.field().dirty();
|
|
552
|
+
}
|
|
553
|
+
get dirty() {
|
|
554
|
+
return this.field().dirty();
|
|
555
|
+
}
|
|
556
|
+
get touched() {
|
|
557
|
+
return this.field().touched();
|
|
558
|
+
}
|
|
559
|
+
get untouched() {
|
|
560
|
+
return !this.field().touched();
|
|
561
|
+
}
|
|
562
|
+
get status() {
|
|
563
|
+
if (this.field().disabled()) {
|
|
564
|
+
return 'DISABLED';
|
|
704
565
|
}
|
|
705
|
-
if (
|
|
706
|
-
|
|
707
|
-
return getOption(config.error, ctx);
|
|
708
|
-
} else {
|
|
709
|
-
return emailError({
|
|
710
|
-
message: getOption(config?.message, ctx)
|
|
711
|
-
});
|
|
712
|
-
}
|
|
566
|
+
if (this.field().valid()) {
|
|
567
|
+
return 'VALID';
|
|
713
568
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
function max(path, maxValue, config) {
|
|
719
|
-
const MAX_MEMO = metadata(path, createMetadataKey(), ctx => typeof maxValue === 'number' ? maxValue : maxValue(ctx));
|
|
720
|
-
metadata(path, MAX, ({
|
|
721
|
-
state
|
|
722
|
-
}) => state.metadata(MAX_MEMO)());
|
|
723
|
-
validate(path, ctx => {
|
|
724
|
-
if (isEmpty(ctx.value())) {
|
|
725
|
-
return undefined;
|
|
569
|
+
if (this.field().invalid()) {
|
|
570
|
+
return 'INVALID';
|
|
726
571
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
return undefined;
|
|
572
|
+
if (this.field().pending()) {
|
|
573
|
+
return 'PENDING';
|
|
730
574
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return maxError(max, {
|
|
738
|
-
message: getOption(config?.message, ctx)
|
|
739
|
-
});
|
|
740
|
-
}
|
|
575
|
+
throw new _RuntimeError(1910, ngDevMode && 'Unknown form control status');
|
|
576
|
+
}
|
|
577
|
+
valueAccessor = null;
|
|
578
|
+
hasValidator(validator) {
|
|
579
|
+
if (validator === Validators.required) {
|
|
580
|
+
return this.field().required();
|
|
741
581
|
}
|
|
742
|
-
return
|
|
743
|
-
}
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
updateValueAndValidity() {}
|
|
744
585
|
}
|
|
745
586
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
587
|
+
const FIELD_STATE_KEY_TO_CONTROL_BINDING = {
|
|
588
|
+
disabled: 'disabled',
|
|
589
|
+
disabledReasons: 'disabledReasons',
|
|
590
|
+
dirty: 'dirty',
|
|
591
|
+
errors: 'errors',
|
|
592
|
+
hidden: 'hidden',
|
|
593
|
+
invalid: 'invalid',
|
|
594
|
+
max: 'max',
|
|
595
|
+
maxLength: 'maxLength',
|
|
596
|
+
min: 'min',
|
|
597
|
+
minLength: 'minLength',
|
|
598
|
+
name: 'name',
|
|
599
|
+
pattern: 'pattern',
|
|
600
|
+
pending: 'pending',
|
|
601
|
+
readonly: 'readonly',
|
|
602
|
+
required: 'required',
|
|
603
|
+
touched: 'touched'
|
|
604
|
+
};
|
|
605
|
+
const CONTROL_BINDING_TO_FIELD_STATE_KEY = /* @__PURE__ */(() => {
|
|
606
|
+
const map = {};
|
|
607
|
+
for (const key of Object.keys(FIELD_STATE_KEY_TO_CONTROL_BINDING)) {
|
|
608
|
+
map[FIELD_STATE_KEY_TO_CONTROL_BINDING[key]] = key;
|
|
609
|
+
}
|
|
610
|
+
return map;
|
|
611
|
+
})();
|
|
612
|
+
function readFieldStateBindingValue(fieldState, key) {
|
|
613
|
+
const property = CONTROL_BINDING_TO_FIELD_STATE_KEY[key];
|
|
614
|
+
return fieldState[property]?.();
|
|
615
|
+
}
|
|
616
|
+
const CONTROL_BINDING_NAMES = /* @__PURE__ */(() => Object.values(FIELD_STATE_KEY_TO_CONTROL_BINDING))();
|
|
617
|
+
function createBindings() {
|
|
618
|
+
return {};
|
|
619
|
+
}
|
|
620
|
+
function bindingUpdated(bindings, key, value) {
|
|
621
|
+
if (bindings[key] !== value) {
|
|
622
|
+
bindings[key] = value;
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function isNativeFormElement(element) {
|
|
629
|
+
return element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA';
|
|
630
|
+
}
|
|
631
|
+
function isNumericFormElement(element) {
|
|
632
|
+
if (element.tagName !== 'INPUT') {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
const type = element.type;
|
|
636
|
+
return type === 'date' || type === 'datetime-local' || type === 'month' || type === 'number' || type === 'range' || type === 'time' || type === 'week';
|
|
637
|
+
}
|
|
638
|
+
function isTextualFormElement(element) {
|
|
639
|
+
return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';
|
|
640
|
+
}
|
|
641
|
+
function getNativeControlValue(element, currentValue) {
|
|
642
|
+
let modelValue;
|
|
643
|
+
if (element.validity.badInput) {
|
|
644
|
+
return {
|
|
645
|
+
errors: [new NativeInputParseError()]
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
switch (element.type) {
|
|
649
|
+
case 'checkbox':
|
|
650
|
+
return {
|
|
651
|
+
value: element.checked
|
|
652
|
+
};
|
|
653
|
+
case 'number':
|
|
654
|
+
case 'range':
|
|
655
|
+
case 'datetime-local':
|
|
656
|
+
modelValue = untracked(currentValue);
|
|
657
|
+
if (typeof modelValue === 'number' || modelValue === null) {
|
|
658
|
+
return {
|
|
659
|
+
value: element.value === '' ? null : element.valueAsNumber
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
break;
|
|
663
|
+
case 'date':
|
|
664
|
+
case 'month':
|
|
665
|
+
case 'time':
|
|
666
|
+
case 'week':
|
|
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
|
+
};
|
|
676
|
+
}
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
value: element.value
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function setNativeControlValue(element, value) {
|
|
684
|
+
switch (element.type) {
|
|
685
|
+
case 'checkbox':
|
|
686
|
+
element.checked = value;
|
|
687
|
+
return;
|
|
688
|
+
case 'radio':
|
|
689
|
+
element.checked = value === element.value;
|
|
690
|
+
return;
|
|
691
|
+
case 'number':
|
|
692
|
+
case 'range':
|
|
693
|
+
case 'datetime-local':
|
|
694
|
+
if (typeof value === 'number') {
|
|
695
|
+
setNativeNumberControlValue(element, value);
|
|
696
|
+
return;
|
|
697
|
+
} else if (value === null) {
|
|
698
|
+
element.value = '';
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
break;
|
|
702
|
+
case 'date':
|
|
703
|
+
case 'month':
|
|
704
|
+
case 'time':
|
|
705
|
+
case 'week':
|
|
706
|
+
if (value === null || value instanceof Date) {
|
|
707
|
+
element.valueAsDate = value;
|
|
708
|
+
return;
|
|
709
|
+
} else if (typeof value === 'number') {
|
|
710
|
+
setNativeNumberControlValue(element, value);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
element.value = value;
|
|
715
|
+
}
|
|
716
|
+
function setNativeNumberControlValue(element, value) {
|
|
717
|
+
if (isNaN(value)) {
|
|
718
|
+
element.value = '';
|
|
719
|
+
} else {
|
|
720
|
+
element.valueAsNumber = value;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function setNativeDomProperty(renderer, element, name, value) {
|
|
724
|
+
switch (name) {
|
|
725
|
+
case 'name':
|
|
726
|
+
renderer.setAttribute(element, name, value);
|
|
727
|
+
break;
|
|
728
|
+
case 'disabled':
|
|
729
|
+
case 'readonly':
|
|
730
|
+
case 'required':
|
|
731
|
+
if (value) {
|
|
732
|
+
renderer.setAttribute(element, name, '');
|
|
762
733
|
} else {
|
|
763
|
-
|
|
764
|
-
message: getOption(config?.message, ctx)
|
|
765
|
-
});
|
|
734
|
+
renderer.removeAttribute(element, name);
|
|
766
735
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
736
|
+
break;
|
|
737
|
+
case 'max':
|
|
738
|
+
case 'min':
|
|
739
|
+
case 'minLength':
|
|
740
|
+
case 'maxLength':
|
|
741
|
+
if (value !== undefined) {
|
|
742
|
+
renderer.setAttribute(element, name, value.toString());
|
|
743
|
+
} else {
|
|
744
|
+
renderer.removeAttribute(element, name);
|
|
745
|
+
}
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
770
748
|
}
|
|
771
749
|
|
|
772
|
-
function
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (min === undefined || Number.isNaN(min)) {
|
|
783
|
-
return undefined;
|
|
750
|
+
function customControlCreate(host, parent) {
|
|
751
|
+
host.listenToCustomControlModel(value => parent.state().controlValue.set(value));
|
|
752
|
+
host.listenToCustomControlOutput('touchedChange', () => parent.state().markAsTouched());
|
|
753
|
+
parent.registerAsBinding(host.customControl);
|
|
754
|
+
const bindings = createBindings();
|
|
755
|
+
return () => {
|
|
756
|
+
const state = parent.state();
|
|
757
|
+
const controlValue = state.controlValue();
|
|
758
|
+
if (bindingUpdated(bindings, 'controlValue', controlValue)) {
|
|
759
|
+
host.setCustomControlModelInput(controlValue);
|
|
784
760
|
}
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
return getOption(config.error, ctx);
|
|
761
|
+
for (const name of CONTROL_BINDING_NAMES) {
|
|
762
|
+
let value;
|
|
763
|
+
if (name === 'errors') {
|
|
764
|
+
value = parent.errors();
|
|
790
765
|
} else {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
766
|
+
value = readFieldStateBindingValue(state, name);
|
|
767
|
+
}
|
|
768
|
+
if (bindingUpdated(bindings, name, value)) {
|
|
769
|
+
host.setInputOnDirectives(name, value);
|
|
770
|
+
if (parent.elementAcceptsNativeProperty(name) && !host.customControlHasInput(name)) {
|
|
771
|
+
setNativeDomProperty(parent.renderer, parent.nativeFormElement, name, value);
|
|
772
|
+
}
|
|
794
773
|
}
|
|
795
774
|
}
|
|
796
|
-
|
|
797
|
-
});
|
|
775
|
+
};
|
|
798
776
|
}
|
|
799
777
|
|
|
800
|
-
function
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
if (minLength === undefined) {
|
|
811
|
-
return undefined;
|
|
778
|
+
function cvaControlCreate(host, parent) {
|
|
779
|
+
parent.controlValueAccessor.registerOnChange(value => parent.state().controlValue.set(value));
|
|
780
|
+
parent.controlValueAccessor.registerOnTouched(() => parent.state().markAsTouched());
|
|
781
|
+
parent.registerAsBinding();
|
|
782
|
+
const bindings = createBindings();
|
|
783
|
+
return () => {
|
|
784
|
+
const fieldState = parent.state();
|
|
785
|
+
const value = fieldState.value();
|
|
786
|
+
if (bindingUpdated(bindings, 'controlValue', value)) {
|
|
787
|
+
untracked(() => parent.controlValueAccessor.writeValue(value));
|
|
812
788
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
})
|
|
789
|
+
for (const name of CONTROL_BINDING_NAMES) {
|
|
790
|
+
const value = readFieldStateBindingValue(fieldState, name);
|
|
791
|
+
if (bindingUpdated(bindings, name, value)) {
|
|
792
|
+
const propertyWasSet = host.setInputOnDirectives(name, value);
|
|
793
|
+
if (name === 'disabled' && parent.controlValueAccessor.setDisabledState) {
|
|
794
|
+
untracked(() => parent.controlValueAccessor.setDisabledState(value));
|
|
795
|
+
} else if (!propertyWasSet && parent.elementAcceptsNativeProperty(name)) {
|
|
796
|
+
setNativeDomProperty(parent.renderer, parent.nativeFormElement, name, value);
|
|
797
|
+
}
|
|
820
798
|
}
|
|
821
799
|
}
|
|
822
|
-
|
|
823
|
-
});
|
|
800
|
+
};
|
|
824
801
|
}
|
|
825
802
|
|
|
826
|
-
function
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return undefined;
|
|
803
|
+
function observeSelectMutations(select, onMutation, destroyRef) {
|
|
804
|
+
if (typeof MutationObserver !== 'function') {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const observer = new MutationObserver(mutations => {
|
|
808
|
+
if (mutations.some(m => isRelevantSelectMutation(m))) {
|
|
809
|
+
onMutation();
|
|
834
810
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
811
|
+
});
|
|
812
|
+
observer.observe(select, {
|
|
813
|
+
attributes: true,
|
|
814
|
+
attributeFilter: ['value'],
|
|
815
|
+
characterData: true,
|
|
816
|
+
childList: true,
|
|
817
|
+
subtree: true
|
|
818
|
+
});
|
|
819
|
+
destroyRef.onDestroy(() => observer.disconnect());
|
|
820
|
+
}
|
|
821
|
+
function isRelevantSelectMutation(mutation) {
|
|
822
|
+
if (mutation.type === 'childList' || mutation.type === 'characterData') {
|
|
823
|
+
if (mutation.target instanceof Comment) {
|
|
824
|
+
return false;
|
|
838
825
|
}
|
|
839
|
-
|
|
840
|
-
if (
|
|
841
|
-
return
|
|
842
|
-
} else {
|
|
843
|
-
return patternError(pattern, {
|
|
844
|
-
message: getOption(config?.message, ctx)
|
|
845
|
-
});
|
|
826
|
+
for (const node of mutation.addedNodes) {
|
|
827
|
+
if (!(node instanceof Comment)) {
|
|
828
|
+
return true;
|
|
846
829
|
}
|
|
847
830
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
function required(path, config) {
|
|
853
|
-
const REQUIRED_MEMO = metadata(path, createMetadataKey(), ctx => config?.when ? config.when(ctx) : true);
|
|
854
|
-
metadata(path, REQUIRED, ({
|
|
855
|
-
state
|
|
856
|
-
}) => state.metadata(REQUIRED_MEMO)());
|
|
857
|
-
validate(path, ctx => {
|
|
858
|
-
if (ctx.state.metadata(REQUIRED_MEMO)() && isEmpty(ctx.value())) {
|
|
859
|
-
if (config?.error) {
|
|
860
|
-
return getOption(config.error, ctx);
|
|
861
|
-
} else {
|
|
862
|
-
return requiredError({
|
|
863
|
-
message: getOption(config?.message, ctx)
|
|
864
|
-
});
|
|
831
|
+
for (const node of mutation.removedNodes) {
|
|
832
|
+
if (!(node instanceof Comment)) {
|
|
833
|
+
return true;
|
|
865
834
|
}
|
|
866
835
|
}
|
|
867
|
-
return
|
|
868
|
-
}
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
if (mutation.type === 'attributes' && mutation.target instanceof HTMLOptionElement) {
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
return false;
|
|
869
842
|
}
|
|
870
843
|
|
|
871
|
-
function
|
|
872
|
-
|
|
873
|
-
const
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
844
|
+
function nativeControlCreate(host, parent, parseErrorsSource) {
|
|
845
|
+
let updateMode = false;
|
|
846
|
+
const input = parent.nativeFormElement;
|
|
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));
|
|
850
|
+
host.listenToDom('blur', () => parent.state().markAsTouched());
|
|
851
|
+
parent.registerAsBinding();
|
|
852
|
+
if (input.tagName === 'SELECT') {
|
|
853
|
+
observeSelectMutations(input, () => {
|
|
854
|
+
if (!updateMode) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
input.value = parent.state().controlValue();
|
|
858
|
+
}, parent.destroyRef);
|
|
859
|
+
}
|
|
860
|
+
const bindings = createBindings();
|
|
861
|
+
return () => {
|
|
862
|
+
const state = parent.state();
|
|
863
|
+
const controlValue = state.controlValue();
|
|
864
|
+
if (bindingUpdated(bindings, 'controlValue', controlValue)) {
|
|
865
|
+
setNativeControlValue(input, controlValue);
|
|
880
866
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
case 'idle':
|
|
888
|
-
return undefined;
|
|
889
|
-
case 'loading':
|
|
890
|
-
case 'reloading':
|
|
891
|
-
return 'pending';
|
|
892
|
-
case 'resolved':
|
|
893
|
-
case 'local':
|
|
894
|
-
if (!res.hasValue()) {
|
|
895
|
-
return undefined;
|
|
867
|
+
for (const name of CONTROL_BINDING_NAMES) {
|
|
868
|
+
const value = readFieldStateBindingValue(state, name);
|
|
869
|
+
if (bindingUpdated(bindings, name, value)) {
|
|
870
|
+
host.setInputOnDirectives(name, value);
|
|
871
|
+
if (parent.elementAcceptsNativeProperty(name)) {
|
|
872
|
+
setNativeDomProperty(parent.renderer, input, name, value);
|
|
896
873
|
}
|
|
897
|
-
|
|
898
|
-
return addDefaultField(errors, ctx.fieldTree);
|
|
899
|
-
case 'error':
|
|
900
|
-
errors = opts.onError(res.error(), ctx);
|
|
901
|
-
return addDefaultField(errors, ctx.fieldTree);
|
|
874
|
+
}
|
|
902
875
|
}
|
|
903
|
-
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
function validateTree(path, logic) {
|
|
907
|
-
assertPathIsCurrent(path);
|
|
908
|
-
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
909
|
-
pathNode.builder.addSyncTreeErrorRule(ctx => addDefaultField(logic(ctx), ctx.fieldTree));
|
|
876
|
+
updateMode = true;
|
|
877
|
+
};
|
|
910
878
|
}
|
|
911
879
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
880
|
+
const ɵNgFieldDirective = Symbol();
|
|
881
|
+
const FORM_FIELD = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FORM_FIELD' : '');
|
|
882
|
+
class FormField {
|
|
883
|
+
field = input.required({
|
|
884
|
+
...(ngDevMode ? {
|
|
885
|
+
debugName: "field"
|
|
886
|
+
} : {}),
|
|
887
|
+
alias: 'formField'
|
|
917
888
|
});
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
889
|
+
renderer = inject(Renderer2);
|
|
890
|
+
destroyRef = inject(DestroyRef);
|
|
891
|
+
state = computed(() => this.field()(), ...(ngDevMode ? [{
|
|
892
|
+
debugName: "state"
|
|
893
|
+
}] : []));
|
|
894
|
+
injector = inject(Injector);
|
|
895
|
+
element = inject(ElementRef).nativeElement;
|
|
896
|
+
elementIsNativeFormElement = isNativeFormElement(this.element);
|
|
897
|
+
elementAcceptsNumericValues = isNumericFormElement(this.element);
|
|
898
|
+
elementAcceptsTextualValues = isTextualFormElement(this.element);
|
|
899
|
+
nativeFormElement = this.elementIsNativeFormElement ? this.element : undefined;
|
|
900
|
+
focuser = options => this.element.focus(options);
|
|
901
|
+
controlValueAccessors = inject(NG_VALUE_ACCESSOR, {
|
|
902
|
+
optional: true,
|
|
903
|
+
self: true
|
|
927
904
|
});
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
905
|
+
config = inject(SIGNAL_FORMS_CONFIG, {
|
|
906
|
+
optional: true
|
|
907
|
+
});
|
|
908
|
+
parseErrorsSource = signal(undefined, ...(ngDevMode ? [{
|
|
909
|
+
debugName: "parseErrorsSource"
|
|
910
|
+
}] : []));
|
|
911
|
+
_interopNgControl;
|
|
912
|
+
get interopNgControl() {
|
|
913
|
+
return this._interopNgControl ??= new InteropNgControl(this.state);
|
|
914
|
+
}
|
|
915
|
+
parseErrors = computed(() => this.parseErrorsSource()?.().map(err => ({
|
|
916
|
+
...err,
|
|
917
|
+
fieldTree: untracked(this.state).fieldTree,
|
|
918
|
+
formField: this
|
|
919
|
+
})) ?? [], ...(ngDevMode ? [{
|
|
920
|
+
debugName: "parseErrors"
|
|
921
|
+
}] : []));
|
|
922
|
+
errors = computed(() => this.state().errors().filter(err => !err.formField || err.formField === this), ...(ngDevMode ? [{
|
|
923
|
+
debugName: "errors"
|
|
924
|
+
}] : []));
|
|
925
|
+
isFieldBinding = false;
|
|
926
|
+
get controlValueAccessor() {
|
|
927
|
+
return this.controlValueAccessors?.[0] ?? this.interopNgControl?.valueAccessor ?? undefined;
|
|
928
|
+
}
|
|
929
|
+
installClassBindingEffect() {
|
|
930
|
+
const classes = Object.entries(this.config?.classes ?? {}).map(([className, computation]) => [className, computed(() => computation(this))]);
|
|
931
|
+
if (classes.length === 0) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
const bindings = createBindings();
|
|
935
|
+
afterRenderEffect({
|
|
936
|
+
write: () => {
|
|
937
|
+
for (const [className, computation] of classes) {
|
|
938
|
+
const active = computation();
|
|
939
|
+
if (bindingUpdated(bindings, className, active)) {
|
|
940
|
+
if (active) {
|
|
941
|
+
this.renderer.addClass(this.element, className);
|
|
942
|
+
} else {
|
|
943
|
+
this.renderer.removeClass(this.element, className);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}, {
|
|
949
|
+
injector: this.injector
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
focus(options) {
|
|
953
|
+
this.focuser(options);
|
|
954
|
+
}
|
|
955
|
+
registerAsBinding(bindingOptions) {
|
|
956
|
+
if (this.isFieldBinding) {
|
|
957
|
+
throw new _RuntimeError(1913, typeof ngDevMode !== 'undefined' && ngDevMode && 'FormField already registered as a binding');
|
|
958
|
+
}
|
|
959
|
+
this.isFieldBinding = true;
|
|
960
|
+
this.installClassBindingEffect();
|
|
961
|
+
if (bindingOptions?.focus) {
|
|
962
|
+
this.focuser = focusOptions => bindingOptions.focus(focusOptions);
|
|
963
|
+
}
|
|
964
|
+
effect(onCleanup => {
|
|
965
|
+
const fieldNode = this.state();
|
|
966
|
+
fieldNode.nodeState.formFieldBindings.update(controls => [...controls, this]);
|
|
967
|
+
onCleanup(() => {
|
|
968
|
+
fieldNode.nodeState.formFieldBindings.update(controls => controls.filter(c => c !== this));
|
|
969
|
+
});
|
|
970
|
+
}, {
|
|
971
|
+
injector: this.injector
|
|
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
|
|
941
982
|
});
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
[ɵNgFieldDirective];
|
|
986
|
+
ɵngControlCreate(host) {
|
|
987
|
+
if (host.hasPassThrough) {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
if (this.controlValueAccessor) {
|
|
991
|
+
this.ɵngControlUpdate = cvaControlCreate(host, this);
|
|
992
|
+
} else if (host.customControl) {
|
|
993
|
+
this.ɵngControlUpdate = customControlCreate(host, this);
|
|
994
|
+
} else if (this.elementIsNativeFormElement) {
|
|
995
|
+
this.ɵngControlUpdate = nativeControlCreate(host, this, this.parseErrorsSource);
|
|
996
|
+
} else {
|
|
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.`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
ɵngControlUpdate;
|
|
1001
|
+
elementAcceptsNativeProperty(key) {
|
|
1002
|
+
if (!this.elementIsNativeFormElement) {
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
switch (key) {
|
|
1006
|
+
case 'min':
|
|
1007
|
+
case 'max':
|
|
1008
|
+
return this.elementAcceptsNumericValues;
|
|
1009
|
+
case 'minLength':
|
|
1010
|
+
case 'maxLength':
|
|
1011
|
+
return this.elementAcceptsTextualValues;
|
|
1012
|
+
case 'disabled':
|
|
1013
|
+
case 'required':
|
|
1014
|
+
case 'readonly':
|
|
1015
|
+
case 'name':
|
|
1016
|
+
return true;
|
|
1017
|
+
default:
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
1022
|
+
minVersion: "12.0.0",
|
|
1023
|
+
version: "21.2.0-rc.0",
|
|
1024
|
+
ngImport: i0,
|
|
1025
|
+
type: FormField,
|
|
1026
|
+
deps: [],
|
|
1027
|
+
target: i0.ɵɵFactoryTarget.Directive
|
|
1028
|
+
});
|
|
1029
|
+
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
1030
|
+
minVersion: "17.1.0",
|
|
1031
|
+
version: "21.2.0-rc.0",
|
|
1032
|
+
type: FormField,
|
|
1033
|
+
isStandalone: true,
|
|
1034
|
+
selector: "[formField]",
|
|
1035
|
+
inputs: {
|
|
1036
|
+
field: {
|
|
1037
|
+
classPropertyName: "field",
|
|
1038
|
+
publicName: "formField",
|
|
1039
|
+
isSignal: true,
|
|
1040
|
+
isRequired: true,
|
|
1041
|
+
transformFunction: null
|
|
1042
|
+
}
|
|
942
1043
|
},
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1044
|
+
providers: [{
|
|
1045
|
+
provide: FORM_FIELD,
|
|
1046
|
+
useExisting: FormField
|
|
1047
|
+
}, {
|
|
1048
|
+
provide: NgControl,
|
|
1049
|
+
useFactory: () => inject(FormField).interopNgControl
|
|
1050
|
+
}, {
|
|
1051
|
+
provide: FORM_FIELD_PARSE_ERRORS,
|
|
1052
|
+
useFactory: () => inject(FormField).parseErrorsSource
|
|
1053
|
+
}],
|
|
1054
|
+
exportAs: ["formField"],
|
|
1055
|
+
controlCreate: {
|
|
1056
|
+
passThroughInput: "formField"
|
|
947
1057
|
},
|
|
948
|
-
|
|
1058
|
+
ngImport: i0
|
|
949
1059
|
});
|
|
950
1060
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1061
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
1062
|
+
minVersion: "12.0.0",
|
|
1063
|
+
version: "21.2.0-rc.0",
|
|
1064
|
+
ngImport: i0,
|
|
1065
|
+
type: FormField,
|
|
1066
|
+
decorators: [{
|
|
1067
|
+
type: Directive,
|
|
1068
|
+
args: [{
|
|
1069
|
+
selector: '[formField]',
|
|
1070
|
+
exportAs: 'formField',
|
|
1071
|
+
providers: [{
|
|
1072
|
+
provide: FORM_FIELD,
|
|
1073
|
+
useExisting: FormField
|
|
1074
|
+
}, {
|
|
1075
|
+
provide: NgControl,
|
|
1076
|
+
useFactory: () => inject(FormField).interopNgControl
|
|
1077
|
+
}, {
|
|
1078
|
+
provide: FORM_FIELD_PARSE_ERRORS,
|
|
1079
|
+
useFactory: () => inject(FormField).parseErrorsSource
|
|
1080
|
+
}]
|
|
1081
|
+
}]
|
|
1082
|
+
}],
|
|
1083
|
+
propDecorators: {
|
|
1084
|
+
field: [{
|
|
1085
|
+
type: i0.Input,
|
|
1086
|
+
args: [{
|
|
1087
|
+
isSignal: true,
|
|
1088
|
+
alias: "formField",
|
|
1089
|
+
required: true
|
|
1090
|
+
}]
|
|
1091
|
+
}]
|
|
970
1092
|
}
|
|
971
|
-
}
|
|
1093
|
+
});
|
|
972
1094
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1095
|
+
class FormRoot {
|
|
1096
|
+
fieldTree = input.required({
|
|
1097
|
+
...(ngDevMode ? {
|
|
1098
|
+
debugName: "fieldTree"
|
|
1099
|
+
} : {}),
|
|
1100
|
+
alias: 'formRoot'
|
|
1101
|
+
});
|
|
1102
|
+
onSubmit(event) {
|
|
1103
|
+
event.preventDefault();
|
|
1104
|
+
submit(this.fieldTree());
|
|
1105
|
+
}
|
|
1106
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
1107
|
+
minVersion: "12.0.0",
|
|
1108
|
+
version: "21.2.0-rc.0",
|
|
1109
|
+
ngImport: i0,
|
|
1110
|
+
type: FormRoot,
|
|
1111
|
+
deps: [],
|
|
1112
|
+
target: i0.ɵɵFactoryTarget.Directive
|
|
1113
|
+
});
|
|
1114
|
+
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
1115
|
+
minVersion: "17.1.0",
|
|
1116
|
+
version: "21.2.0-rc.0",
|
|
1117
|
+
type: FormRoot,
|
|
1118
|
+
isStandalone: true,
|
|
1119
|
+
selector: "form[formRoot]",
|
|
1120
|
+
inputs: {
|
|
1121
|
+
fieldTree: {
|
|
1122
|
+
classPropertyName: "fieldTree",
|
|
1123
|
+
publicName: "formRoot",
|
|
1124
|
+
isSignal: true,
|
|
1125
|
+
isRequired: true,
|
|
1126
|
+
transformFunction: null
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
host: {
|
|
1130
|
+
attributes: {
|
|
1131
|
+
"novalidate": ""
|
|
1132
|
+
},
|
|
1133
|
+
listeners: {
|
|
1134
|
+
"submit": "onSubmit($event)"
|
|
1135
|
+
}
|
|
1136
|
+
},
|
|
1137
|
+
ngImport: i0
|
|
979
1138
|
});
|
|
980
1139
|
}
|
|
1140
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
1141
|
+
minVersion: "12.0.0",
|
|
1142
|
+
version: "21.2.0-rc.0",
|
|
1143
|
+
ngImport: i0,
|
|
1144
|
+
type: FormRoot,
|
|
1145
|
+
decorators: [{
|
|
1146
|
+
type: Directive,
|
|
1147
|
+
args: [{
|
|
1148
|
+
selector: 'form[formRoot]',
|
|
1149
|
+
host: {
|
|
1150
|
+
'novalidate': '',
|
|
1151
|
+
'(submit)': 'onSubmit($event)'
|
|
1152
|
+
}
|
|
1153
|
+
}]
|
|
1154
|
+
}],
|
|
1155
|
+
propDecorators: {
|
|
1156
|
+
fieldTree: [{
|
|
1157
|
+
type: i0.Input,
|
|
1158
|
+
args: [{
|
|
1159
|
+
isSignal: true,
|
|
1160
|
+
alias: "formRoot",
|
|
1161
|
+
required: true
|
|
1162
|
+
}]
|
|
1163
|
+
}]
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
981
1166
|
|
|
982
|
-
|
|
983
|
-
assertPathIsCurrent(path);
|
|
984
|
-
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
985
|
-
const debouncer = typeof durationOrDebouncer === 'function' ? durationOrDebouncer : durationOrDebouncer > 0 ? debounceForDuration(durationOrDebouncer) : immediate;
|
|
986
|
-
pathNode.builder.addMetadataRule(DEBOUNCER, () => debouncer);
|
|
987
|
-
}
|
|
988
|
-
function debounceForDuration(durationInMilliseconds) {
|
|
989
|
-
return (_context, abortSignal) => {
|
|
990
|
-
return new Promise(resolve => {
|
|
991
|
-
let timeoutId;
|
|
992
|
-
const onAbort = () => {
|
|
993
|
-
clearTimeout(timeoutId);
|
|
994
|
-
resolve();
|
|
995
|
-
};
|
|
996
|
-
timeoutId = setTimeout(() => {
|
|
997
|
-
abortSignal.removeEventListener('abort', onAbort);
|
|
998
|
-
resolve();
|
|
999
|
-
}, durationInMilliseconds);
|
|
1000
|
-
abortSignal.addEventListener('abort', onAbort, {
|
|
1001
|
-
once: true
|
|
1002
|
-
});
|
|
1003
|
-
});
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
function immediate() {}
|
|
1007
|
-
|
|
1008
|
-
export { BaseNgValidationError, EmailValidationError, FORM_FIELD, FormField, 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, 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 };
|
|
1009
1168
|
//# sourceMappingURL=signals.mjs.map
|