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