@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.
@@ -1,15 +1,15 @@
1
1
  /**
2
- * @license Angular v21.2.0-next.2
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, inject, Renderer2, DestroyRef, computed, Injector, ElementRef, signal, afterRenderEffect, effect, Directive, ɵisPromise as _isPromise, resource } from '@angular/core';
9
- import { Validators, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
10
- import { signalErrorsToValidationErrors, assertPathIsCurrent, FieldPathNode, addDefaultField, metadata, createMetadataKey, MAX, MAX_LENGTH, MIN, MIN_LENGTH, PATTERN, REQUIRED, createManagedMetadataKey, DEBOUNCER } from './_structure-chunk.mjs';
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
- class InteropNgControl {
25
- field;
26
- constructor(field) {
27
- this.field = field;
28
- }
29
- control = this;
30
- get value() {
31
- return this.field().value();
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
- throw new _RuntimeError(1910, ngDevMode && 'Unknown form control status');
77
- }
78
- valueAccessor = null;
79
- hasValidator(validator) {
80
- if (validator === Validators.required) {
81
- return this.field().required();
34
+ if (typeof result === 'string') {
35
+ return {
36
+ fieldTree: ctx.fieldTree,
37
+ message: result
38
+ };
82
39
  }
83
- return false;
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
- updateValueAndValidity() {}
69
+ return value === '' || value === false || value == null;
86
70
  }
87
71
 
88
- function isNativeFormElement(element) {
89
- return element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA';
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
- function isNumericFormElement(element) {
92
- if (element.tagName !== 'INPUT') {
93
- return false;
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
- function isTextualFormElement(element) {
99
- return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';
112
+ class RequiredValidationError extends BaseNgValidationError {
113
+ kind = 'required';
100
114
  }
101
- function getNativeControlValue(element, currentValue) {
102
- switch (element.type) {
103
- case 'checkbox':
104
- return element.checked;
105
- case 'number':
106
- case 'range':
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
- function setNativeControlValue(element, value) {
127
- switch (element.type) {
128
- case 'checkbox':
129
- element.checked = value;
130
- return;
131
- case 'radio':
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
- function setNativeNumberControlValue(element, value) {
157
- if (isNaN(value)) {
158
- element.value = '';
159
- } else {
160
- element.valueAsNumber = value;
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
- function setNativeDomProperty(renderer, element, name, value) {
164
- switch (name) {
165
- case 'name':
166
- renderer.setAttribute(element, name, value);
167
- break;
168
- case 'disabled':
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
- const FIELD_STATE_KEY_TO_CONTROL_BINDING = {
191
- disabled: 'disabled',
192
- disabledReasons: 'disabledReasons',
193
- dirty: 'dirty',
194
- errors: 'errors',
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
- const CONTROL_BINDING_NAMES = /* @__PURE__ */(() => Object.values(FIELD_STATE_KEY_TO_CONTROL_BINDING))();
220
- function createBindings() {
221
- return {};
155
+ class EmailValidationError extends BaseNgValidationError {
156
+ kind = 'email';
222
157
  }
223
- function bindingUpdated(bindings, key, value) {
224
- if (bindings[key] !== value) {
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
- function cvaControlCreate(host, parent) {
232
- parent.controlValueAccessor.registerOnChange(value => parent.state().controlValue.set(value));
233
- parent.controlValueAccessor.registerOnTouched(() => parent.state().markAsTouched());
234
- parent.registerAsBinding();
235
- const bindings = createBindings();
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
- for (const name of CONTROL_BINDING_NAMES) {
243
- const value = readFieldStateBindingValue(fieldState, name);
244
- if (bindingUpdated(bindings, name, value)) {
245
- const propertyWasSet = host.setInputOnDirectives(name, value);
246
- if (name === 'disabled' && parent.controlValueAccessor.setDisabledState) {
247
- untracked(() => parent.controlValueAccessor.setDisabledState(value));
248
- } else if (!propertyWasSet && parent.elementAcceptsNativeProperty(name)) {
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 customControlCreate(host, parent) {
257
- host.listenToCustomControlModel(value => parent.state().controlValue.set(value));
258
- host.listenToCustomControlOutput('touchedChange', () => parent.state().markAsTouched());
259
- parent.registerAsBinding(host.customControl);
260
- const bindings = createBindings();
261
- return () => {
262
- const state = parent.state();
263
- const controlValue = state.controlValue();
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
- for (const name of CONTROL_BINDING_NAMES) {
268
- let value;
269
- if (name === 'errors') {
270
- value = parent.errors();
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
- value = readFieldStateBindingValue(state, name);
273
- }
274
- if (bindingUpdated(bindings, name, value)) {
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 observeSelectMutations(select, onMutation, destroyRef) {
285
- if (typeof MutationObserver !== 'function') {
286
- return;
287
- }
288
- const observer = new MutationObserver(mutations => {
289
- if (mutations.some(m => isRelevantSelectMutation(m))) {
290
- onMutation();
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
- function isRelevantSelectMutation(mutation) {
303
- if (mutation.type === 'childList' || mutation.type === 'characterData') {
304
- if (mutation.target instanceof Comment) {
305
- return false;
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
- for (const node of mutation.addedNodes) {
308
- if (!(node instanceof Comment)) {
309
- return true;
310
- }
245
+ const min = ctx.state.metadata(MIN_MEMO)();
246
+ if (min === undefined || Number.isNaN(min)) {
247
+ return undefined;
311
248
  }
312
- for (const node of mutation.removedNodes) {
313
- if (!(node instanceof Comment)) {
314
- return true;
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 false;
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 nativeControlCreate(host, parent) {
326
- let updateMode = false;
327
- const input = parent.nativeFormElement;
328
- host.listenToDom('input', () => {
329
- const state = parent.state();
330
- state.controlValue.set(getNativeControlValue(input, state.value));
331
- });
332
- host.listenToDom('blur', () => parent.state().markAsTouched());
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
- for (const name of CONTROL_BINDING_NAMES) {
350
- const value = readFieldStateBindingValue(state, name);
351
- if (bindingUpdated(bindings, name, value)) {
352
- host.setInputOnDirectives(name, value);
353
- if (parent.elementAcceptsNativeProperty(name)) {
354
- setNativeDomProperty(parent.renderer, input, name, value);
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
- updateMode = true;
359
- };
286
+ return undefined;
287
+ });
360
288
  }
361
289
 
362
- const ɵNgFieldDirective = Symbol();
363
- const FORM_FIELD = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FORM_FIELD' : '');
364
- class FormField {
365
- fieldTree = input.required({
366
- ...(ngDevMode ? {
367
- debugName: "fieldTree"
368
- } : {}),
369
- alias: 'formField'
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
- ɵngControlUpdate;
475
- elementAcceptsNativeProperty(key) {
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
- switch (key) {
480
- case 'min':
481
- case 'max':
482
- return this.elementAcceptsNumericValues;
483
- case 'minLength':
484
- case 'maxLength':
485
- return this.elementAcceptsTextualValues;
486
- case 'disabled':
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
- static ɵdir = i0.ɵɵngDeclareDirective({
504
- minVersion: "17.1.0",
505
- version: "21.2.0-next.2",
506
- type: FormField,
507
- isStandalone: true,
508
- selector: "[formField]",
509
- inputs: {
510
- fieldTree: {
511
- classPropertyName: "fieldTree",
512
- publicName: "formField",
513
- isSignal: true,
514
- isRequired: true,
515
- transformFunction: null
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
- providers: [{
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 disabled(path, logic) {
335
+ function validateAsync(path, opts) {
564
336
  assertPathIsCurrent(path);
565
337
  const pathNode = FieldPathNode.unwrapFieldPath(path);
566
- pathNode.builder.addDisabledReasonRule(ctx => {
567
- let result = true;
568
- if (typeof logic === 'string') {
569
- result = logic;
570
- } else if (logic) {
571
- result = logic(ctx);
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
- if (typeof result === 'string') {
574
- return {
575
- fieldTree: ctx.fieldTree,
576
- message: result
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 hidden(path, logic) {
370
+ function validateTree(path, logic) {
586
371
  assertPathIsCurrent(path);
587
372
  const pathNode = FieldPathNode.unwrapFieldPath(path);
588
- pathNode.builder.addHiddenRule(logic);
373
+ pathNode.builder.addSyncTreeErrorRule(ctx => addDefaultField(logic(ctx), ctx.fieldTree));
589
374
  }
590
375
 
591
- function readonly(path, logic = () => true) {
592
- assertPathIsCurrent(path);
593
- const pathNode = FieldPathNode.unwrapFieldPath(path);
594
- pathNode.builder.addReadonlyRule(logic);
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
- function getLengthOrSize(value) {
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 getOption(opt, ctx) {
602
- return opt instanceof Function ? opt(ctx) : opt;
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
- function isEmpty(value) {
605
- if (typeof value === 'number') {
606
- return isNaN(value);
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 validate(path, logic) {
612
- assertPathIsCurrent(path);
613
- const pathNode = FieldPathNode.unwrapFieldPath(path);
614
- pathNode.builder.addSyncErrorRule(ctx => {
615
- return addDefaultField(logic(ctx), ctx.fieldTree);
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 requiredError(options) {
620
- return new RequiredValidationError(options);
621
- }
622
- function minError(min, options) {
623
- return new MinValidationError(min, options);
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 emailError(options) {
638
- return new EmailValidationError(options);
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
- class BaseNgValidationError {
641
- __brand = undefined;
642
- kind = '';
643
- fieldTree;
644
- message;
645
- constructor(options) {
646
- if (options) {
647
- Object.assign(this, options);
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
- class RequiredValidationError extends BaseNgValidationError {
652
- kind = 'required';
487
+ };
488
+ return {
489
+ errors: errors.asReadonly(),
490
+ setRawValue
491
+ };
653
492
  }
654
- class MinValidationError extends BaseNgValidationError {
655
- min;
656
- kind = 'min';
657
- constructor(min, options) {
658
- super(options);
659
- this.min = min;
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
- class MaxValidationError extends BaseNgValidationError {
663
- max;
664
- kind = 'max';
665
- constructor(max, options) {
666
- super(options);
667
- this.max = max;
522
+
523
+ class InteropNgControl {
524
+ field;
525
+ constructor(field) {
526
+ this.field = field;
668
527
  }
669
- }
670
- class MinLengthValidationError extends BaseNgValidationError {
671
- minLength;
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
- class MaxLengthValidationError extends BaseNgValidationError {
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
- class PatternValidationError extends BaseNgValidationError {
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
- class EmailValidationError extends BaseNgValidationError {
695
- kind = 'email';
696
- }
697
- const NgValidationError = BaseNgValidationError;
698
-
699
- 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])?)*$/;
700
- function email(path, config) {
701
- validate(path, ctx => {
702
- if (isEmpty(ctx.value())) {
703
- return undefined;
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 (!EMAIL_REGEXP.test(ctx.value())) {
706
- if (config?.error) {
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
- return undefined;
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
- const max = ctx.state.metadata(MAX_MEMO)();
728
- if (max === undefined || Number.isNaN(max)) {
729
- return undefined;
572
+ if (this.field().pending()) {
573
+ return 'PENDING';
730
574
  }
731
- const value = ctx.value();
732
- const numValue = !value && value !== 0 ? NaN : Number(value);
733
- if (numValue > max) {
734
- if (config?.error) {
735
- return getOption(config.error, ctx);
736
- } else {
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 undefined;
743
- });
582
+ return false;
583
+ }
584
+ updateValueAndValidity() {}
744
585
  }
745
586
 
746
- function maxLength(path, maxLength, config) {
747
- const MAX_LENGTH_MEMO = metadata(path, createMetadataKey(), ctx => typeof maxLength === 'number' ? maxLength : maxLength(ctx));
748
- metadata(path, MAX_LENGTH, ({
749
- state
750
- }) => state.metadata(MAX_LENGTH_MEMO)());
751
- validate(path, ctx => {
752
- if (isEmpty(ctx.value())) {
753
- return undefined;
754
- }
755
- const maxLength = ctx.state.metadata(MAX_LENGTH_MEMO)();
756
- if (maxLength === undefined) {
757
- return undefined;
758
- }
759
- if (getLengthOrSize(ctx.value()) > maxLength) {
760
- if (config?.error) {
761
- return getOption(config.error, ctx);
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
- return maxLengthError(maxLength, {
764
- message: getOption(config?.message, ctx)
765
- });
734
+ renderer.removeAttribute(element, name);
766
735
  }
767
- }
768
- return undefined;
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 min(path, minValue, config) {
773
- const MIN_MEMO = metadata(path, createMetadataKey(), ctx => typeof minValue === 'number' ? minValue : minValue(ctx));
774
- metadata(path, MIN, ({
775
- state
776
- }) => state.metadata(MIN_MEMO)());
777
- validate(path, ctx => {
778
- if (isEmpty(ctx.value())) {
779
- return undefined;
780
- }
781
- const min = ctx.state.metadata(MIN_MEMO)();
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 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);
761
+ for (const name of CONTROL_BINDING_NAMES) {
762
+ let value;
763
+ if (name === 'errors') {
764
+ value = parent.errors();
790
765
  } else {
791
- return minError(min, {
792
- message: getOption(config?.message, ctx)
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
- return undefined;
797
- });
775
+ };
798
776
  }
799
777
 
800
- function minLength(path, minLength, config) {
801
- const MIN_LENGTH_MEMO = metadata(path, createMetadataKey(), ctx => typeof minLength === 'number' ? minLength : minLength(ctx));
802
- metadata(path, MIN_LENGTH, ({
803
- state
804
- }) => state.metadata(MIN_LENGTH_MEMO)());
805
- validate(path, ctx => {
806
- if (isEmpty(ctx.value())) {
807
- return undefined;
808
- }
809
- const minLength = ctx.state.metadata(MIN_LENGTH_MEMO)();
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
- if (getLengthOrSize(ctx.value()) < minLength) {
814
- if (config?.error) {
815
- return getOption(config.error, ctx);
816
- } else {
817
- return minLengthError(minLength, {
818
- message: getOption(config?.message, ctx)
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
- return undefined;
823
- });
800
+ };
824
801
  }
825
802
 
826
- function pattern(path, pattern, config) {
827
- const PATTERN_MEMO = metadata(path, createMetadataKey(), ctx => pattern instanceof RegExp ? pattern : pattern(ctx));
828
- metadata(path, PATTERN, ({
829
- state
830
- }) => state.metadata(PATTERN_MEMO)());
831
- validate(path, ctx => {
832
- if (isEmpty(ctx.value())) {
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
- const pattern = ctx.state.metadata(PATTERN_MEMO)();
836
- if (pattern === undefined) {
837
- return undefined;
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
- if (!pattern.test(ctx.value())) {
840
- if (config?.error) {
841
- return getOption(config.error, ctx);
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
- return undefined;
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 undefined;
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 validateAsync(path, opts) {
872
- assertPathIsCurrent(path);
873
- const pathNode = FieldPathNode.unwrapFieldPath(path);
874
- const RESOURCE = createManagedMetadataKey(opts.factory);
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;
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
- return opts.params(ctx);
882
- });
883
- pathNode.builder.addAsyncErrorRule(ctx => {
884
- const res = ctx.state.metadata(RESOURCE);
885
- let errors;
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;
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
- 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);
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
- function validateStandardSchema(path, schema) {
913
- const VALIDATOR_MEMO = metadata(path, createMetadataKey(), ({
914
- value
915
- }) => {
916
- return schema['~standard'].validate(value());
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
- validateTree(path, ({
919
- state,
920
- fieldTreeOf
921
- }) => {
922
- const result = state.metadata(VALIDATOR_MEMO)();
923
- if (_isPromise(result)) {
924
- return [];
925
- }
926
- return result?.issues?.map(issue => standardIssueToFormTreeError(fieldTreeOf(path), issue)) ?? [];
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
- validateAsync(path, {
929
- params: ({
930
- state
931
- }) => {
932
- const result = state.metadata(VALIDATOR_MEMO)();
933
- return _isPromise(result) ? result : undefined;
934
- },
935
- factory: params => {
936
- return resource({
937
- params,
938
- loader: async ({
939
- params
940
- }) => (await params)?.issues ?? []
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
- onSuccess: (issues, {
944
- fieldTreeOf
945
- }) => {
946
- return issues.map(issue => standardIssueToFormTreeError(fieldTreeOf(path), issue));
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
- onError: () => {}
1058
+ ngImport: i0
949
1059
  });
950
1060
  }
951
- function standardSchemaError(issue, options) {
952
- return new StandardSchemaValidationError(issue, options);
953
- }
954
- function standardIssueToFormTreeError(fieldTree, issue) {
955
- let target = fieldTree;
956
- for (const pathPart of issue.path ?? []) {
957
- const pathKey = typeof pathPart === 'object' ? pathPart.key : pathPart;
958
- target = target[pathKey];
959
- }
960
- return addDefaultField(standardSchemaError(issue, {
961
- message: issue.message
962
- }), target);
963
- }
964
- class StandardSchemaValidationError extends BaseNgValidationError {
965
- issue;
966
- kind = 'standardSchema';
967
- constructor(issue, options) {
968
- super(options);
969
- this.issue = issue;
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
- function validateHttp(path, opts) {
974
- validateAsync(path, {
975
- params: opts.request,
976
- factory: request => httpResource(request, opts.options),
977
- onSuccess: opts.onSuccess,
978
- onError: opts.onError
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
- function debounce(path, durationOrDebouncer) {
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