@angular/forms 21.2.0-next.1 → 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.
@@ -1,15 +1,15 @@
1
1
  /**
2
- * @license Angular v21.2.0-next.1
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, inject, ElementRef, Injector, input, computed, signal, ɵCONTROL as _CONTROL, untracked, effect, Directive, ɵɵcontrolCreate as __controlCreate, ɵcontrolUpdate as _controlUpdate, ɵisPromise as _isPromise, resource } from '@angular/core';
9
- import { Validators, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
10
- import { 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, 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,212 +21,6 @@ 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
- const errors = this.field().errors();
50
- if (errors.length === 0) {
51
- return null;
52
- }
53
- const errObj = {};
54
- for (const error of errors) {
55
- errObj[error.kind] = error;
56
- }
57
- return errObj;
58
- }
59
- get pristine() {
60
- return !this.field().dirty();
61
- }
62
- get dirty() {
63
- return this.field().dirty();
64
- }
65
- get touched() {
66
- return this.field().touched();
67
- }
68
- get untouched() {
69
- return !this.field().touched();
70
- }
71
- get status() {
72
- if (this.field().disabled()) {
73
- return 'DISABLED';
74
- }
75
- if (this.field().valid()) {
76
- return 'VALID';
77
- }
78
- if (this.field().invalid()) {
79
- return 'INVALID';
80
- }
81
- if (this.field().pending()) {
82
- return 'PENDING';
83
- }
84
- throw new _RuntimeError(1910, ngDevMode && 'Unknown form control status');
85
- }
86
- valueAccessor = null;
87
- hasValidator(validator) {
88
- if (validator === Validators.required) {
89
- return this.field().required();
90
- }
91
- return false;
92
- }
93
- updateValueAndValidity() {}
94
- }
95
-
96
- const FORM_FIELD = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FORM_FIELD' : '');
97
- const controlInstructions = {
98
- create: __controlCreate,
99
- update: _controlUpdate
100
- };
101
- class FormField {
102
- element = inject(ElementRef).nativeElement;
103
- injector = inject(Injector);
104
- fieldTree = input.required({
105
- ...(ngDevMode ? {
106
- debugName: "fieldTree"
107
- } : {}),
108
- alias: 'formField'
109
- });
110
- state = computed(() => this.fieldTree()(), ...(ngDevMode ? [{
111
- debugName: "state"
112
- }] : []));
113
- bindingOptions = signal(undefined, ...(ngDevMode ? [{
114
- debugName: "bindingOptions"
115
- }] : []));
116
- parseErrors = computed(() => this.bindingOptions()?.parseErrors?.().map(err => ({
117
- ...err,
118
- fieldTree: this.fieldTree(),
119
- formField: this
120
- })) ?? [], ...(ngDevMode ? [{
121
- debugName: "parseErrors"
122
- }] : []));
123
- errors = computed(() => this.state().errors().filter(err => !err.formField || err.formField === this), ...(ngDevMode ? [{
124
- debugName: "errors"
125
- }] : []));
126
- [_CONTROL] = controlInstructions;
127
- config = inject(SIGNAL_FORMS_CONFIG, {
128
- optional: true
129
- });
130
- classes = Object.entries(this.config?.classes ?? {}).map(([className, computation]) => [className, computed(() => computation(this))]);
131
- controlValueAccessors = inject(NG_VALUE_ACCESSOR, {
132
- optional: true,
133
- self: true
134
- });
135
- interopNgControl;
136
- get ɵinteropControl() {
137
- return this.controlValueAccessors?.[0] ?? this.interopNgControl?.valueAccessor ?? undefined;
138
- }
139
- getOrCreateNgControl() {
140
- return this.interopNgControl ??= new InteropNgControl(this.state);
141
- }
142
- registerAsBinding(bindingOptions) {
143
- if (untracked(this.bindingOptions)) {
144
- throw new _RuntimeError(1913, ngDevMode && 'FormField already registered as a binding');
145
- }
146
- this.bindingOptions.set(bindingOptions);
147
- effect(onCleanup => {
148
- const fieldNode = this.state();
149
- fieldNode.nodeState.formFieldBindings.update(controls => [...controls, this]);
150
- onCleanup(() => {
151
- fieldNode.nodeState.formFieldBindings.update(controls => controls.filter(c => c !== this));
152
- });
153
- }, {
154
- injector: this.injector
155
- });
156
- }
157
- focus(options) {
158
- const bindingOptions = untracked(this.bindingOptions);
159
- if (bindingOptions?.focus) {
160
- bindingOptions.focus(options);
161
- } else {
162
- this.element.focus(options);
163
- }
164
- }
165
- static ɵfac = i0.ɵɵngDeclareFactory({
166
- minVersion: "12.0.0",
167
- version: "21.2.0-next.1",
168
- ngImport: i0,
169
- type: FormField,
170
- deps: [],
171
- target: i0.ɵɵFactoryTarget.Directive
172
- });
173
- static ɵdir = i0.ɵɵngDeclareDirective({
174
- minVersion: "17.1.0",
175
- version: "21.2.0-next.1",
176
- type: FormField,
177
- isStandalone: true,
178
- selector: "[formField]",
179
- inputs: {
180
- fieldTree: {
181
- classPropertyName: "fieldTree",
182
- publicName: "formField",
183
- isSignal: true,
184
- isRequired: true,
185
- transformFunction: null
186
- }
187
- },
188
- providers: [{
189
- provide: FORM_FIELD,
190
- useExisting: FormField
191
- }, {
192
- provide: NgControl,
193
- useFactory: () => inject(FormField).getOrCreateNgControl()
194
- }],
195
- exportAs: ["formField"],
196
- ngImport: i0
197
- });
198
- }
199
- i0.ɵɵngDeclareClassMetadata({
200
- minVersion: "12.0.0",
201
- version: "21.2.0-next.1",
202
- ngImport: i0,
203
- type: FormField,
204
- decorators: [{
205
- type: Directive,
206
- args: [{
207
- selector: '[formField]',
208
- exportAs: 'formField',
209
- providers: [{
210
- provide: FORM_FIELD,
211
- useExisting: FormField
212
- }, {
213
- provide: NgControl,
214
- useFactory: () => inject(FormField).getOrCreateNgControl()
215
- }]
216
- }]
217
- }],
218
- propDecorators: {
219
- fieldTree: [{
220
- type: i0.Input,
221
- args: [{
222
- isSignal: true,
223
- alias: "formField",
224
- required: true
225
- }]
226
- }]
227
- }
228
- });
229
-
230
24
  function disabled(path, logic) {
231
25
  assertPathIsCurrent(path);
232
26
  const pathNode = FieldPathNode.unwrapFieldPath(path);
@@ -304,10 +98,7 @@ function patternError(pattern, options) {
304
98
  function emailError(options) {
305
99
  return new EmailValidationError(options);
306
100
  }
307
- function standardSchemaError(issue, options) {
308
- return new StandardSchemaValidationError(issue, options);
309
- }
310
- class _NgValidationError {
101
+ class BaseNgValidationError {
311
102
  __brand = undefined;
312
103
  kind = '';
313
104
  fieldTree;
@@ -318,10 +109,10 @@ class _NgValidationError {
318
109
  }
319
110
  }
320
111
  }
321
- class RequiredValidationError extends _NgValidationError {
112
+ class RequiredValidationError extends BaseNgValidationError {
322
113
  kind = 'required';
323
114
  }
324
- class MinValidationError extends _NgValidationError {
115
+ class MinValidationError extends BaseNgValidationError {
325
116
  min;
326
117
  kind = 'min';
327
118
  constructor(min, options) {
@@ -329,7 +120,7 @@ class MinValidationError extends _NgValidationError {
329
120
  this.min = min;
330
121
  }
331
122
  }
332
- class MaxValidationError extends _NgValidationError {
123
+ class MaxValidationError extends BaseNgValidationError {
333
124
  max;
334
125
  kind = 'max';
335
126
  constructor(max, options) {
@@ -337,7 +128,7 @@ class MaxValidationError extends _NgValidationError {
337
128
  this.max = max;
338
129
  }
339
130
  }
340
- class MinLengthValidationError extends _NgValidationError {
131
+ class MinLengthValidationError extends BaseNgValidationError {
341
132
  minLength;
342
133
  kind = 'minLength';
343
134
  constructor(minLength, options) {
@@ -345,7 +136,7 @@ class MinLengthValidationError extends _NgValidationError {
345
136
  this.minLength = minLength;
346
137
  }
347
138
  }
348
- class MaxLengthValidationError extends _NgValidationError {
139
+ class MaxLengthValidationError extends BaseNgValidationError {
349
140
  maxLength;
350
141
  kind = 'maxLength';
351
142
  constructor(maxLength, options) {
@@ -353,7 +144,7 @@ class MaxLengthValidationError extends _NgValidationError {
353
144
  this.maxLength = maxLength;
354
145
  }
355
146
  }
356
- class PatternValidationError extends _NgValidationError {
147
+ class PatternValidationError extends BaseNgValidationError {
357
148
  pattern;
358
149
  kind = 'pattern';
359
150
  constructor(pattern, options) {
@@ -361,18 +152,10 @@ class PatternValidationError extends _NgValidationError {
361
152
  this.pattern = pattern;
362
153
  }
363
154
  }
364
- class EmailValidationError extends _NgValidationError {
155
+ class EmailValidationError extends BaseNgValidationError {
365
156
  kind = 'email';
366
157
  }
367
- class StandardSchemaValidationError extends _NgValidationError {
368
- issue;
369
- kind = 'standardSchema';
370
- constructor(issue, options) {
371
- super(options);
372
- this.issue = issue;
373
- }
374
- }
375
- const NgValidationError = _NgValidationError;
158
+ const NgValidationError = BaseNgValidationError;
376
159
 
377
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])?)*$/;
378
161
  function email(path, config) {
@@ -588,17 +371,16 @@ function validateTree(path, logic) {
588
371
  }
589
372
 
590
373
  function validateStandardSchema(path, schema) {
591
- const VALIDATOR_MEMO = metadata(path, createMetadataKey(), ({
592
- value
593
- }) => {
594
- return schema['~standard'].validate(value());
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;
595
377
  });
596
378
  validateTree(path, ({
597
379
  state,
598
380
  fieldTreeOf
599
381
  }) => {
600
382
  const result = state.metadata(VALIDATOR_MEMO)();
601
- if (_isPromise(result)) {
383
+ if (!result || _isPromise(result)) {
602
384
  return [];
603
385
  }
604
386
  return result?.issues?.map(issue => standardIssueToFormTreeError(fieldTreeOf(path), issue)) ?? [];
@@ -608,7 +390,7 @@ function validateStandardSchema(path, schema) {
608
390
  state
609
391
  }) => {
610
392
  const result = state.metadata(VALIDATOR_MEMO)();
611
- return _isPromise(result) ? result : undefined;
393
+ return result && _isPromise(result) ? result : undefined;
612
394
  },
613
395
  factory: params => {
614
396
  return resource({
@@ -626,6 +408,9 @@ function validateStandardSchema(path, schema) {
626
408
  onError: () => {}
627
409
  });
628
410
  }
411
+ function standardSchemaError(issue, options) {
412
+ return new StandardSchemaValidationError(issue, options);
413
+ }
629
414
  function standardIssueToFormTreeError(fieldTree, issue) {
630
415
  let target = fieldTree;
631
416
  for (const pathPart of issue.path ?? []) {
@@ -636,6 +421,14 @@ function standardIssueToFormTreeError(fieldTree, issue) {
636
421
  message: issue.message
637
422
  }), target);
638
423
  }
424
+ class StandardSchemaValidationError extends BaseNgValidationError {
425
+ issue;
426
+ kind = 'standardSchema';
427
+ constructor(issue, options) {
428
+ super(options);
429
+ this.issue = issue;
430
+ }
431
+ }
639
432
 
640
433
  function validateHttp(path, opts) {
641
434
  validateAsync(path, {
@@ -658,6 +451,7 @@ function debounceForDuration(durationInMilliseconds) {
658
451
  let timeoutId;
659
452
  const onAbort = () => {
660
453
  clearTimeout(timeoutId);
454
+ resolve();
661
455
  };
662
456
  timeoutId = setTimeout(() => {
663
457
  abortSignal.removeEventListener('abort', onAbort);
@@ -671,5 +465,656 @@ function debounceForDuration(durationInMilliseconds) {
671
465
  }
672
466
  function immediate() {}
673
467
 
674
- export { 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 };
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;
503
+ }
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();
563
+ }
564
+ return false;
565
+ }
566
+ updateValueAndValidity() {}
567
+ }
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;
591
+ }
592
+ return map;
593
+ })();
594
+ function readFieldStateBindingValue(fieldState, key) {
595
+ const property = CONTROL_BINDING_TO_FIELD_STATE_KEY[key];
596
+ return fieldState[property]?.();
597
+ }
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;
606
+ }
607
+ return false;
608
+ }
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;
616
+ }
617
+ const type = element.type;
618
+ return type === 'date' || type === 'datetime-local' || type === 'month' || type === 'number' || type === 'range' || type === 'time' || type === 'week';
619
+ }
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;
645
+ }
646
+ return element.value;
647
+ }
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
+ }
675
+ }
676
+ element.value = value;
677
+ }
678
+ function setNativeNumberControlValue(element, value) {
679
+ if (isNaN(value)) {
680
+ element.value = '';
681
+ } else {
682
+ element.valueAsNumber = value;
683
+ }
684
+ }
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, '');
695
+ } else {
696
+ renderer.removeAttribute(element, name);
697
+ }
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
+ }
710
+ }
711
+
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);
722
+ }
723
+ for (const name of CONTROL_BINDING_NAMES) {
724
+ let value;
725
+ if (name === 'errors') {
726
+ value = parent.errors();
727
+ } else {
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
+ }
735
+ }
736
+ }
737
+ };
738
+ }
739
+
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
+ }
760
+ }
761
+ }
762
+ };
763
+ }
764
+
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();
772
+ }
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());
782
+ }
783
+ function isRelevantSelectMutation(mutation) {
784
+ if (mutation.type === 'childList' || mutation.type === 'characterData') {
785
+ if (mutation.target instanceof Comment) {
786
+ return false;
787
+ }
788
+ for (const node of mutation.addedNodes) {
789
+ if (!(node instanceof Comment)) {
790
+ return true;
791
+ }
792
+ }
793
+ for (const node of mutation.removedNodes) {
794
+ if (!(node instanceof Comment)) {
795
+ return true;
796
+ }
797
+ }
798
+ return false;
799
+ }
800
+ if (mutation.type === 'attributes' && mutation.target instanceof HTMLOptionElement) {
801
+ return true;
802
+ }
803
+ return false;
804
+ }
805
+
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);
829
+ }
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
+ }
837
+ }
838
+ }
839
+ updateMode = true;
840
+ };
841
+ }
842
+
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
+ }
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;
941
+ }
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.`);
950
+ }
951
+ }
952
+ ɵngControlUpdate;
953
+ elementAcceptsNativeProperty(key) {
954
+ if (!this.elementIsNativeFormElement) {
955
+ return false;
956
+ }
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;
971
+ }
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
980
+ });
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
+ }
995
+ },
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"
1009
+ },
1010
+ ngImport: i0
1011
+ });
1012
+ }
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
+ }]
1044
+ }
1045
+ });
1046
+
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
1090
+ });
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
+ });
1118
+
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 };
675
1120
  //# sourceMappingURL=signals.mjs.map