@angular/forms 21.0.4 → 21.1.0-next.1

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,28 +1,34 @@
1
1
  /**
2
- * @license Angular v21.0.4
2
+ * @license Angular v21.1.0-next.1
3
3
  * (c) 2010-2025 Google LLC. https://angular.dev/
4
4
  * License: MIT
5
5
  */
6
6
 
7
- import { untracked, computed, runInInjectionContext, Injector, linkedSignal, signal, APP_ID, effect, inject } from '@angular/core';
7
+ import { computed, untracked, runInInjectionContext, linkedSignal, Injector, signal, APP_ID, effect, inject } from '@angular/core';
8
8
  import { AbstractControl } from '@angular/forms';
9
9
  import { SIGNAL } from '@angular/core/primitives/signals';
10
10
 
11
- let boundPathDepth = 0;
12
- function getBoundPathDepth() {
13
- return boundPathDepth;
11
+ function isArray(value) {
12
+ return Array.isArray(value);
14
13
  }
15
- function setBoundPathDepthForResolution(fn, depth) {
16
- return (...args) => {
17
- try {
18
- boundPathDepth = depth;
19
- return fn(...args);
20
- } finally {
21
- boundPathDepth = 0;
22
- }
23
- };
14
+ function isObject(value) {
15
+ return (typeof value === 'object' || typeof value === 'function') && value != null;
24
16
  }
25
17
 
18
+ function reduceChildren(node, initialValue, fn, shortCircuit) {
19
+ const childrenMap = node.structure.childrenMap();
20
+ if (!childrenMap) {
21
+ return initialValue;
22
+ }
23
+ let value = initialValue;
24
+ for (const child of childrenMap.values()) {
25
+ if (shortCircuit?.(value)) {
26
+ break;
27
+ }
28
+ value = fn(child, value);
29
+ }
30
+ return value;
31
+ }
26
32
  function shortCircuitFalse(value) {
27
33
  return !value;
28
34
  }
@@ -36,11 +42,131 @@ function getInjectorFromOptions(options) {
36
42
  return options.parent.structure.root.structure.injector;
37
43
  }
38
44
 
39
- function isArray(value) {
40
- return Array.isArray(value);
45
+ function calculateValidationSelfStatus(state) {
46
+ if (state.errors().length > 0) {
47
+ return 'invalid';
48
+ }
49
+ if (state.pending()) {
50
+ return 'unknown';
51
+ }
52
+ return 'valid';
41
53
  }
42
- function isObject(value) {
43
- return (typeof value === 'object' || typeof value === 'function') && value != null;
54
+ class FieldValidationState {
55
+ node;
56
+ constructor(node) {
57
+ this.node = node;
58
+ }
59
+ rawSyncTreeErrors = computed(() => {
60
+ if (this.shouldSkipValidation()) {
61
+ return [];
62
+ }
63
+ return [...this.node.logicNode.logic.syncTreeErrors.compute(this.node.context), ...(this.node.structure.parent?.validationState.rawSyncTreeErrors() ?? [])];
64
+ }, ...(ngDevMode ? [{
65
+ debugName: "rawSyncTreeErrors"
66
+ }] : []));
67
+ syncErrors = computed(() => {
68
+ if (this.shouldSkipValidation()) {
69
+ return [];
70
+ }
71
+ return [...this.node.logicNode.logic.syncErrors.compute(this.node.context), ...this.syncTreeErrors(), ...normalizeErrors(this.node.submitState.serverErrors())];
72
+ }, ...(ngDevMode ? [{
73
+ debugName: "syncErrors"
74
+ }] : []));
75
+ syncValid = computed(() => {
76
+ if (this.shouldSkipValidation()) {
77
+ return true;
78
+ }
79
+ return reduceChildren(this.node, this.syncErrors().length === 0, (child, value) => value && child.validationState.syncValid(), shortCircuitFalse);
80
+ }, ...(ngDevMode ? [{
81
+ debugName: "syncValid"
82
+ }] : []));
83
+ syncTreeErrors = computed(() => this.rawSyncTreeErrors().filter(err => err.field === this.node.fieldProxy), ...(ngDevMode ? [{
84
+ debugName: "syncTreeErrors"
85
+ }] : []));
86
+ rawAsyncErrors = computed(() => {
87
+ if (this.shouldSkipValidation()) {
88
+ return [];
89
+ }
90
+ return [...this.node.logicNode.logic.asyncErrors.compute(this.node.context), ...(this.node.structure.parent?.validationState.rawAsyncErrors() ?? [])];
91
+ }, ...(ngDevMode ? [{
92
+ debugName: "rawAsyncErrors"
93
+ }] : []));
94
+ asyncErrors = computed(() => {
95
+ if (this.shouldSkipValidation()) {
96
+ return [];
97
+ }
98
+ return this.rawAsyncErrors().filter(err => err === 'pending' || err.field === this.node.fieldProxy);
99
+ }, ...(ngDevMode ? [{
100
+ debugName: "asyncErrors"
101
+ }] : []));
102
+ errors = computed(() => [...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], ...(ngDevMode ? [{
103
+ debugName: "errors"
104
+ }] : []));
105
+ errorSummary = computed(() => reduceChildren(this.node, this.errors(), (child, result) => [...result, ...child.errorSummary()]), ...(ngDevMode ? [{
106
+ debugName: "errorSummary"
107
+ }] : []));
108
+ pending = computed(() => reduceChildren(this.node, this.asyncErrors().includes('pending'), (child, value) => value || child.validationState.asyncErrors().includes('pending')), ...(ngDevMode ? [{
109
+ debugName: "pending"
110
+ }] : []));
111
+ status = computed(() => {
112
+ if (this.shouldSkipValidation()) {
113
+ return 'valid';
114
+ }
115
+ let ownStatus = calculateValidationSelfStatus(this);
116
+ return reduceChildren(this.node, ownStatus, (child, value) => {
117
+ if (value === 'invalid' || child.validationState.status() === 'invalid') {
118
+ return 'invalid';
119
+ } else if (value === 'unknown' || child.validationState.status() === 'unknown') {
120
+ return 'unknown';
121
+ }
122
+ return 'valid';
123
+ }, v => v === 'invalid');
124
+ }, ...(ngDevMode ? [{
125
+ debugName: "status"
126
+ }] : []));
127
+ valid = computed(() => this.status() === 'valid', ...(ngDevMode ? [{
128
+ debugName: "valid"
129
+ }] : []));
130
+ invalid = computed(() => this.status() === 'invalid', ...(ngDevMode ? [{
131
+ debugName: "invalid"
132
+ }] : []));
133
+ shouldSkipValidation = computed(() => this.node.hidden() || this.node.disabled() || this.node.readonly(), ...(ngDevMode ? [{
134
+ debugName: "shouldSkipValidation"
135
+ }] : []));
136
+ }
137
+ function normalizeErrors(error) {
138
+ if (error === undefined) {
139
+ return [];
140
+ }
141
+ if (isArray(error)) {
142
+ return error;
143
+ }
144
+ return [error];
145
+ }
146
+ function addDefaultField(errors, field) {
147
+ if (isArray(errors)) {
148
+ for (const error of errors) {
149
+ error.field ??= field;
150
+ }
151
+ } else if (errors) {
152
+ errors.field ??= field;
153
+ }
154
+ return errors;
155
+ }
156
+
157
+ let boundPathDepth = 0;
158
+ function getBoundPathDepth() {
159
+ return boundPathDepth;
160
+ }
161
+ function setBoundPathDepthForResolution(fn, depth) {
162
+ return (...args) => {
163
+ try {
164
+ boundPathDepth = depth;
165
+ return fn(...args);
166
+ } finally {
167
+ boundPathDepth = 0;
168
+ }
169
+ };
44
170
  }
45
171
 
46
172
  const DYNAMIC = Symbol();
@@ -103,10 +229,10 @@ class ArrayMergeLogic extends ArrayMergeIgnoreLogic {
103
229
  super(predicates, undefined);
104
230
  }
105
231
  }
106
- class MetadataMergeLogic extends AbstractLogic {
232
+ class AggregateMetadataMergeLogic extends AbstractLogic {
107
233
  key;
108
234
  get defaultValue() {
109
- return this.key.reducer.getInitial();
235
+ return this.key.getInitial();
110
236
  }
111
237
  constructor(predicates, key) {
112
238
  super(predicates);
@@ -114,13 +240,13 @@ class MetadataMergeLogic extends AbstractLogic {
114
240
  }
115
241
  compute(ctx) {
116
242
  if (this.fns.length === 0) {
117
- return this.key.reducer.getInitial();
243
+ return this.key.getInitial();
118
244
  }
119
- let acc = this.key.reducer.getInitial();
245
+ let acc = this.key.getInitial();
120
246
  for (let i = 0; i < this.fns.length; i++) {
121
247
  const item = this.fns[i](ctx);
122
248
  if (item !== IGNORED) {
123
- acc = this.key.reducer.reduce(acc, item);
249
+ acc = this.key.reduce(acc, item);
124
250
  }
125
251
  }
126
252
  return acc;
@@ -152,7 +278,8 @@ class LogicContainer {
152
278
  syncErrors;
153
279
  syncTreeErrors;
154
280
  asyncErrors;
155
- metadata = new Map();
281
+ aggregateMetadataKeys = new Map();
282
+ metadataFactories = new Map();
156
283
  constructor(predicates) {
157
284
  this.predicates = predicates;
158
285
  this.hidden = new BooleanOrLogic(predicates);
@@ -162,17 +289,26 @@ class LogicContainer {
162
289
  this.syncTreeErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
163
290
  this.asyncErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
164
291
  }
165
- hasMetadata(key) {
166
- return this.metadata.has(key);
292
+ hasAggregateMetadata(key) {
293
+ return this.aggregateMetadataKeys.has(key);
167
294
  }
168
- getMetadataKeys() {
169
- return this.metadata.keys();
295
+ getAggregateMetadataEntries() {
296
+ return this.aggregateMetadataKeys.entries();
170
297
  }
171
- getMetadata(key) {
172
- if (!this.metadata.has(key)) {
173
- this.metadata.set(key, new MetadataMergeLogic(this.predicates, key));
298
+ getMetadataFactoryEntries() {
299
+ return this.metadataFactories.entries();
300
+ }
301
+ getAggregateMetadata(key) {
302
+ if (!this.aggregateMetadataKeys.has(key)) {
303
+ this.aggregateMetadataKeys.set(key, new AggregateMetadataMergeLogic(this.predicates, key));
174
304
  }
175
- return this.metadata.get(key);
305
+ return this.aggregateMetadataKeys.get(key);
306
+ }
307
+ addMetadataFactory(key, factory) {
308
+ if (this.metadataFactories.has(key)) {
309
+ throw new Error(`Can't define value twice for the same MetadataKey`);
310
+ }
311
+ this.metadataFactories.set(key, factory);
176
312
  }
177
313
  mergeIn(other) {
178
314
  this.hidden.mergeIn(other.hidden);
@@ -181,9 +317,11 @@ class LogicContainer {
181
317
  this.syncErrors.mergeIn(other.syncErrors);
182
318
  this.syncTreeErrors.mergeIn(other.syncTreeErrors);
183
319
  this.asyncErrors.mergeIn(other.asyncErrors);
184
- for (const key of other.getMetadataKeys()) {
185
- const metadataLogic = other.metadata.get(key);
186
- this.getMetadata(key).mergeIn(metadataLogic);
320
+ for (const [key, metadataLogic] of other.getAggregateMetadataEntries()) {
321
+ this.getAggregateMetadata(key).mergeIn(metadataLogic);
322
+ }
323
+ for (const [key, metadataFactory] of other.getMetadataFactoryEntries()) {
324
+ this.addMetadataFactory(key, metadataFactory);
187
325
  }
188
326
  }
189
327
  }
@@ -221,8 +359,11 @@ class LogicNodeBuilder extends AbstractLogicNodeBuilder {
221
359
  addAsyncErrorRule(logic) {
222
360
  this.getCurrent().addAsyncErrorRule(logic);
223
361
  }
224
- addMetadataRule(key, logic) {
225
- this.getCurrent().addMetadataRule(key, logic);
362
+ addAggregateMetadataRule(key, logic) {
363
+ this.getCurrent().addAggregateMetadataRule(key, logic);
364
+ }
365
+ addMetadataFactory(key, factory) {
366
+ this.getCurrent().addMetadataFactory(key, factory);
226
367
  }
227
368
  getChild(key) {
228
369
  if (key === DYNAMIC) {
@@ -294,8 +435,11 @@ class NonMergeableLogicNodeBuilder extends AbstractLogicNodeBuilder {
294
435
  addAsyncErrorRule(logic) {
295
436
  this.logic.asyncErrors.push(setBoundPathDepthForResolution(logic, this.depth));
296
437
  }
297
- addMetadataRule(key, logic) {
298
- this.logic.getMetadata(key).push(setBoundPathDepthForResolution(logic, this.depth));
438
+ addAggregateMetadataRule(key, logic) {
439
+ this.logic.getAggregateMetadata(key).push(setBoundPathDepthForResolution(logic, this.depth));
440
+ }
441
+ addMetadataFactory(key, factory) {
442
+ this.logic.addMetadataFactory(key, setBoundPathDepthForResolution(factory, this.depth));
299
443
  }
300
444
  getChild(key) {
301
445
  if (!this.children.has(key)) {
@@ -513,196 +657,64 @@ function assertPathIsCurrent(path) {
513
657
  }
514
658
  }
515
659
 
516
- function metadata(path, key, logic) {
517
- assertPathIsCurrent(path);
518
- const pathNode = FieldPathNode.unwrapFieldPath(path);
519
- pathNode.builder.addMetadataRule(key, logic);
520
- return key;
660
+ class MetadataKey {
661
+ brand;
662
+ constructor() {}
521
663
  }
522
- const MetadataReducer = {
523
- list() {
524
- return {
525
- reduce: (acc, item) => item === undefined ? acc : [...acc, item],
526
- getInitial: () => []
527
- };
528
- },
529
- min() {
530
- return {
531
- reduce: (acc, item) => {
532
- if (acc === undefined || item === undefined) {
533
- return acc ?? item;
534
- }
535
- return Math.min(acc, item);
536
- },
537
- getInitial: () => undefined
538
- };
539
- },
540
- max() {
541
- return {
542
- reduce: (prev, next) => {
543
- if (prev === undefined || next === undefined) {
544
- return prev ?? next;
545
- }
546
- return Math.max(prev, next);
547
- },
548
- getInitial: () => undefined
549
- };
550
- },
551
- or() {
552
- return {
553
- reduce: (prev, next) => prev || next,
554
- getInitial: () => false
555
- };
556
- },
557
- and() {
558
- return {
559
- reduce: (prev, next) => prev && next,
560
- getInitial: () => true
561
- };
562
- },
563
- override
564
- };
565
- function override(getInitial) {
566
- return {
567
- reduce: (_, item) => item,
568
- getInitial: () => getInitial?.()
569
- };
664
+ function createMetadataKey() {
665
+ return new MetadataKey();
570
666
  }
571
- class MetadataKey {
572
- reducer;
573
- create;
667
+ class AggregateMetadataKey {
668
+ reduce;
669
+ getInitial;
574
670
  brand;
575
- constructor(reducer, create) {
576
- this.reducer = reducer;
577
- this.create = create;
671
+ constructor(reduce, getInitial) {
672
+ this.reduce = reduce;
673
+ this.getInitial = getInitial;
578
674
  }
579
675
  }
580
- function createMetadataKey(reducer) {
581
- return new MetadataKey(reducer ?? MetadataReducer.override());
676
+ function reducedMetadataKey(reduce, getInitial) {
677
+ return new AggregateMetadataKey(reduce, getInitial);
582
678
  }
583
- function createManagedMetadataKey(create, reducer) {
584
- return new MetadataKey(reducer ?? MetadataReducer.override(), create);
585
- }
586
- const REQUIRED = createMetadataKey(MetadataReducer.or());
587
- const MIN = createMetadataKey(MetadataReducer.max());
588
- const MAX = createMetadataKey(MetadataReducer.min());
589
- const MIN_LENGTH = createMetadataKey(MetadataReducer.max());
590
- const MAX_LENGTH = createMetadataKey(MetadataReducer.min());
591
- const PATTERN = createMetadataKey(MetadataReducer.list());
592
-
593
- function calculateValidationSelfStatus(state) {
594
- if (state.errors().length > 0) {
595
- return 'invalid';
596
- }
597
- if (state.pending()) {
598
- return 'unknown';
599
- }
600
- return 'valid';
679
+ function listMetadataKey() {
680
+ return reducedMetadataKey((acc, item) => item === undefined ? acc : [...acc, item], () => []);
601
681
  }
602
- class FieldValidationState {
603
- node;
604
- constructor(node) {
605
- this.node = node;
606
- }
607
- rawSyncTreeErrors = computed(() => {
608
- if (this.shouldSkipValidation()) {
609
- return [];
682
+ function minMetadataKey() {
683
+ return reducedMetadataKey((prev, next) => {
684
+ if (prev === undefined) {
685
+ return next;
610
686
  }
611
- return [...this.node.logicNode.logic.syncTreeErrors.compute(this.node.context), ...(this.node.structure.parent?.validationState.rawSyncTreeErrors() ?? [])];
612
- }, ...(ngDevMode ? [{
613
- debugName: "rawSyncTreeErrors"
614
- }] : []));
615
- syncErrors = computed(() => {
616
- if (this.shouldSkipValidation()) {
617
- return [];
618
- }
619
- return [...this.node.logicNode.logic.syncErrors.compute(this.node.context), ...this.syncTreeErrors(), ...normalizeErrors(this.node.submitState.serverErrors())];
620
- }, ...(ngDevMode ? [{
621
- debugName: "syncErrors"
622
- }] : []));
623
- syncValid = computed(() => {
624
- if (this.shouldSkipValidation()) {
625
- return true;
626
- }
627
- return this.node.structure.reduceChildren(this.syncErrors().length === 0, (child, value) => value && child.validationState.syncValid(), shortCircuitFalse);
628
- }, ...(ngDevMode ? [{
629
- debugName: "syncValid"
630
- }] : []));
631
- syncTreeErrors = computed(() => this.rawSyncTreeErrors().filter(err => err.field === this.node.fieldProxy), ...(ngDevMode ? [{
632
- debugName: "syncTreeErrors"
633
- }] : []));
634
- rawAsyncErrors = computed(() => {
635
- if (this.shouldSkipValidation()) {
636
- return [];
687
+ if (next === undefined) {
688
+ return prev;
637
689
  }
638
- return [...this.node.logicNode.logic.asyncErrors.compute(this.node.context), ...(this.node.structure.parent?.validationState.rawAsyncErrors() ?? [])];
639
- }, ...(ngDevMode ? [{
640
- debugName: "rawAsyncErrors"
641
- }] : []));
642
- asyncErrors = computed(() => {
643
- if (this.shouldSkipValidation()) {
644
- return [];
690
+ return Math.min(prev, next);
691
+ }, () => undefined);
692
+ }
693
+ function maxMetadataKey() {
694
+ return reducedMetadataKey((prev, next) => {
695
+ if (prev === undefined) {
696
+ return next;
645
697
  }
646
- return this.rawAsyncErrors().filter(err => err === 'pending' || err.field === this.node.fieldProxy);
647
- }, ...(ngDevMode ? [{
648
- debugName: "asyncErrors"
649
- }] : []));
650
- errors = computed(() => [...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], ...(ngDevMode ? [{
651
- debugName: "errors"
652
- }] : []));
653
- errorSummary = computed(() => this.node.structure.reduceChildren(this.errors(), (child, result) => [...result, ...child.errorSummary()]), ...(ngDevMode ? [{
654
- debugName: "errorSummary"
655
- }] : []));
656
- pending = computed(() => this.node.structure.reduceChildren(this.asyncErrors().includes('pending'), (child, value) => value || child.validationState.asyncErrors().includes('pending')), ...(ngDevMode ? [{
657
- debugName: "pending"
658
- }] : []));
659
- status = computed(() => {
660
- if (this.shouldSkipValidation()) {
661
- return 'valid';
698
+ if (next === undefined) {
699
+ return prev;
662
700
  }
663
- let ownStatus = calculateValidationSelfStatus(this);
664
- return this.node.structure.reduceChildren(ownStatus, (child, value) => {
665
- if (value === 'invalid' || child.validationState.status() === 'invalid') {
666
- return 'invalid';
667
- } else if (value === 'unknown' || child.validationState.status() === 'unknown') {
668
- return 'unknown';
669
- }
670
- return 'valid';
671
- }, v => v === 'invalid');
672
- }, ...(ngDevMode ? [{
673
- debugName: "status"
674
- }] : []));
675
- valid = computed(() => this.status() === 'valid', ...(ngDevMode ? [{
676
- debugName: "valid"
677
- }] : []));
678
- invalid = computed(() => this.status() === 'invalid', ...(ngDevMode ? [{
679
- debugName: "invalid"
680
- }] : []));
681
- shouldSkipValidation = computed(() => this.node.hidden() || this.node.disabled() || this.node.readonly(), ...(ngDevMode ? [{
682
- debugName: "shouldSkipValidation"
683
- }] : []));
701
+ return Math.max(prev, next);
702
+ }, () => undefined);
684
703
  }
685
- function normalizeErrors(error) {
686
- if (error === undefined) {
687
- return [];
688
- }
689
- if (isArray(error)) {
690
- return error;
691
- }
692
- return [error];
704
+ function orMetadataKey() {
705
+ return reducedMetadataKey((prev, next) => prev || next, () => false);
693
706
  }
694
- function addDefaultField(errors, field) {
695
- if (isArray(errors)) {
696
- for (const error of errors) {
697
- error.field ??= field;
698
- }
699
- } else if (errors) {
700
- errors.field ??= field;
701
- }
702
- return errors;
707
+ function andMetadataKey() {
708
+ return reducedMetadataKey((prev, next) => prev && next, () => true);
703
709
  }
710
+ const REQUIRED = orMetadataKey();
711
+ const MIN = maxMetadataKey();
712
+ const MAX = minMetadataKey();
713
+ const MIN_LENGTH = maxMetadataKey();
714
+ const MAX_LENGTH = minMetadataKey();
715
+ const PATTERN = listMetadataKey();
704
716
 
705
- const DEBOUNCER = createMetadataKey();
717
+ const DEBOUNCER = reducedMetadataKey((_, item) => item, () => undefined);
706
718
 
707
719
  class FieldNodeContext {
708
720
  node;
@@ -777,28 +789,31 @@ class FieldMetadataState {
777
789
  metadata = new Map();
778
790
  constructor(node) {
779
791
  this.node = node;
780
- for (const key of this.node.logicNode.logic.getMetadataKeys()) {
781
- if (key.create) {
782
- const logic = this.node.logicNode.logic.getMetadata(key);
783
- const result = untracked(() => runInInjectionContext(this.node.structure.injector, () => key.create(computed(() => logic.compute(this.node.context)))));
784
- this.metadata.set(key, result);
792
+ untracked(() => runInInjectionContext(this.node.structure.injector, () => {
793
+ for (const [key, factory] of this.node.logicNode.logic.getMetadataFactoryEntries()) {
794
+ this.metadata.set(key, factory(this.node.context));
785
795
  }
786
- }
796
+ }));
787
797
  }
788
798
  get(key) {
789
- if (this.has(key)) {
790
- if (!this.metadata.has(key)) {
791
- if (key.create) {
792
- throw Error('Managed metadata cannot be created lazily');
793
- }
794
- const logic = this.node.logicNode.logic.getMetadata(key);
795
- this.metadata.set(key, computed(() => logic.compute(this.node.context)));
796
- }
799
+ if (key instanceof MetadataKey) {
800
+ return this.metadata.get(key);
801
+ }
802
+ if (!this.metadata.has(key)) {
803
+ const logic = this.node.logicNode.logic.getAggregateMetadata(key);
804
+ const result = computed(() => logic.compute(this.node.context), ...(ngDevMode ? [{
805
+ debugName: "result"
806
+ }] : []));
807
+ this.metadata.set(key, result);
797
808
  }
798
809
  return this.metadata.get(key);
799
810
  }
800
811
  has(key) {
801
- return this.node.logicNode.logic.hasMetadata(key);
812
+ if (key instanceof AggregateMetadataKey) {
813
+ return this.node.logicNode.logic.hasAggregateMetadata(key);
814
+ } else {
815
+ return this.metadata.has(key);
816
+ }
802
817
  }
803
818
  }
804
819
 
@@ -815,10 +830,7 @@ const FIELD_PROXY_HANDLER = {
815
830
  return tgt.value().length;
816
831
  }
817
832
  if (p === Symbol.iterator) {
818
- return () => {
819
- tgt.value();
820
- return Array.prototype[Symbol.iterator].apply(tgt.fieldProxy);
821
- };
833
+ return Array.prototype[p];
822
834
  }
823
835
  }
824
836
  if (isObject(value)) {
@@ -873,8 +885,6 @@ function valueForWrite(sourceValue, newPropValue, prop) {
873
885
 
874
886
  class FieldNodeStructure {
875
887
  logic;
876
- node;
877
- createChildNode;
878
888
  identitySymbol = Symbol();
879
889
  _injector = undefined;
880
890
  get injector() {
@@ -884,146 +894,32 @@ class FieldNodeStructure {
884
894
  });
885
895
  return this._injector;
886
896
  }
887
- constructor(logic, node, createChildNode) {
897
+ constructor(logic) {
888
898
  this.logic = logic;
889
- this.node = node;
890
- this.createChildNode = createChildNode;
891
899
  }
892
900
  children() {
893
- const map = this.childrenMap();
894
- if (map === undefined) {
895
- return [];
896
- }
897
- return Array.from(map.byPropertyKey.values()).map(child => untracked(child.reader));
901
+ return this.childrenMap()?.values() ?? [];
898
902
  }
899
903
  getChild(key) {
900
- const strKey = key.toString();
901
- let reader = untracked(this.childrenMap)?.byPropertyKey.get(strKey)?.reader;
902
- if (!reader) {
903
- reader = this.createReader(strKey);
904
- }
905
- return reader();
906
- }
907
- reduceChildren(initialValue, fn, shortCircuit) {
908
904
  const map = this.childrenMap();
909
- if (!map) {
910
- return initialValue;
905
+ const value = this.value();
906
+ if (!map || !isObject(value)) {
907
+ return undefined;
911
908
  }
912
- let value = initialValue;
913
- for (const child of map.byPropertyKey.values()) {
914
- if (shortCircuit?.(value)) {
915
- break;
909
+ if (isArray(value)) {
910
+ const childValue = value[key];
911
+ if (isObject(childValue) && childValue.hasOwnProperty(this.identitySymbol)) {
912
+ key = childValue[this.identitySymbol];
916
913
  }
917
- value = fn(untracked(child.reader), value);
918
914
  }
919
- return value;
915
+ return map.get(typeof key === 'number' ? key.toString() : key);
920
916
  }
921
917
  destroy() {
922
918
  this.injector.destroy();
923
919
  }
924
- createKeyInParent(options, identityInParent, initialKeyInParent) {
925
- if (options.kind === 'root') {
926
- return ROOT_KEY_IN_PARENT;
927
- }
928
- if (identityInParent === undefined) {
929
- const key = initialKeyInParent;
930
- return computed(() => {
931
- if (this.parent.structure.getChild(key) !== this.node) {
932
- throw new Error(`RuntimeError: orphan field, looking for property '${key}' of ${getDebugName(this.parent)}`);
933
- }
934
- return key;
935
- });
936
- } else {
937
- let lastKnownKey = initialKeyInParent;
938
- return computed(() => {
939
- const parentValue = this.parent.structure.value();
940
- if (!isArray(parentValue)) {
941
- throw new Error(`RuntimeError: orphan field, expected ${getDebugName(this.parent)} to be an array`);
942
- }
943
- const data = parentValue[lastKnownKey];
944
- if (isObject(data) && data.hasOwnProperty(this.parent.structure.identitySymbol) && data[this.parent.structure.identitySymbol] === identityInParent) {
945
- return lastKnownKey;
946
- }
947
- for (let i = 0; i < parentValue.length; i++) {
948
- const data = parentValue[i];
949
- if (isObject(data) && data.hasOwnProperty(this.parent.structure.identitySymbol) && data[this.parent.structure.identitySymbol] === identityInParent) {
950
- return lastKnownKey = i.toString();
951
- }
952
- }
953
- throw new Error(`RuntimeError: orphan field, can't find element in array ${getDebugName(this.parent)}`);
954
- });
955
- }
956
- }
957
- createChildrenMap() {
958
- return linkedSignal({
959
- source: this.value,
960
- computation: (value, previous) => {
961
- if (!isObject(value)) {
962
- return undefined;
963
- }
964
- const prevData = previous?.value ?? {
965
- byPropertyKey: new Map()
966
- };
967
- let data;
968
- const parentIsArray = isArray(value);
969
- if (prevData !== undefined) {
970
- if (parentIsArray) {
971
- data = maybeRemoveStaleArrayFields(prevData, value, this.identitySymbol);
972
- } else {
973
- data = maybeRemoveStaleObjectFields(prevData, value);
974
- }
975
- }
976
- for (const key of Object.keys(value)) {
977
- let trackingKey = undefined;
978
- const childValue = value[key];
979
- if (childValue === undefined) {
980
- if (prevData.byPropertyKey.has(key)) {
981
- data ??= {
982
- ...prevData
983
- };
984
- data.byPropertyKey.delete(key);
985
- }
986
- continue;
987
- }
988
- if (parentIsArray && isObject(childValue) && !isArray(childValue)) {
989
- trackingKey = childValue[this.identitySymbol] ??= Symbol(ngDevMode ? `id:${globalId++}` : '');
990
- }
991
- let childNode;
992
- if (trackingKey) {
993
- if (!prevData.byTrackingKey?.has(trackingKey)) {
994
- data ??= {
995
- ...prevData
996
- };
997
- data.byTrackingKey ??= new Map();
998
- data.byTrackingKey.set(trackingKey, this.createChildNode(key, trackingKey, parentIsArray));
999
- }
1000
- childNode = (data ?? prevData).byTrackingKey.get(trackingKey);
1001
- }
1002
- const child = prevData.byPropertyKey.get(key);
1003
- if (child === undefined) {
1004
- data ??= {
1005
- ...prevData
1006
- };
1007
- data.byPropertyKey.set(key, {
1008
- reader: this.createReader(key),
1009
- node: childNode ?? this.createChildNode(key, trackingKey, parentIsArray)
1010
- });
1011
- } else if (childNode && childNode !== child.node) {
1012
- data ??= {
1013
- ...prevData
1014
- };
1015
- child.node = childNode;
1016
- }
1017
- }
1018
- return data ?? prevData;
1019
- }
1020
- });
1021
- }
1022
- createReader(key) {
1023
- return computed(() => this.childrenMap()?.byPropertyKey.get(key)?.node);
1024
- }
1025
920
  }
1026
921
  class RootFieldNodeStructure extends FieldNodeStructure {
922
+ node;
1027
923
  fieldManager;
1028
924
  value;
1029
925
  get parent() {
@@ -1039,15 +935,15 @@ class RootFieldNodeStructure extends FieldNodeStructure {
1039
935
  return ROOT_KEY_IN_PARENT;
1040
936
  }
1041
937
  childrenMap;
1042
- constructor(node, logic, fieldManager, value, createChildNode) {
1043
- super(logic, node, createChildNode);
938
+ constructor(node, pathNode, logic, fieldManager, value, adapter, createChildNode) {
939
+ super(logic);
940
+ this.node = node;
1044
941
  this.fieldManager = fieldManager;
1045
942
  this.value = value;
1046
- this.childrenMap = this.createChildrenMap();
943
+ this.childrenMap = makeChildrenMapSignal(node, value, this.identitySymbol, pathNode, logic, adapter, createChildNode);
1047
944
  }
1048
945
  }
1049
946
  class ChildFieldNodeStructure extends FieldNodeStructure {
1050
- logic;
1051
947
  parent;
1052
948
  root;
1053
949
  pathKeys;
@@ -1057,25 +953,47 @@ class ChildFieldNodeStructure extends FieldNodeStructure {
1057
953
  get fieldManager() {
1058
954
  return this.root.structure.fieldManager;
1059
955
  }
1060
- constructor(node, logic, parent, identityInParent, initialKeyInParent, createChildNode) {
1061
- super(logic, node, createChildNode);
1062
- this.logic = logic;
956
+ constructor(node, pathNode, logic, parent, identityInParent, initialKeyInParent, adapter, createChildNode) {
957
+ super(logic);
1063
958
  this.parent = parent;
1064
959
  this.root = this.parent.structure.root;
1065
- this.keyInParent = this.createKeyInParent({
1066
- kind: 'child',
1067
- parent,
1068
- pathNode: undefined,
1069
- logic,
1070
- initialKeyInParent,
1071
- identityInParent,
1072
- fieldAdapter: undefined
1073
- }, identityInParent, initialKeyInParent);
1074
960
  this.pathKeys = computed(() => [...parent.structure.pathKeys(), this.keyInParent()], ...(ngDevMode ? [{
1075
961
  debugName: "pathKeys"
1076
962
  }] : []));
963
+ if (identityInParent === undefined) {
964
+ const key = initialKeyInParent;
965
+ this.keyInParent = computed(() => {
966
+ if (parent.structure.childrenMap()?.get(key) !== node) {
967
+ throw new Error(`RuntimeError: orphan field, looking for property '${key}' of ${getDebugName(parent)}`);
968
+ }
969
+ return key;
970
+ }, ...(ngDevMode ? [{
971
+ debugName: "keyInParent"
972
+ }] : []));
973
+ } else {
974
+ let lastKnownKey = initialKeyInParent;
975
+ this.keyInParent = computed(() => {
976
+ const parentValue = parent.structure.value();
977
+ if (!isArray(parentValue)) {
978
+ throw new Error(`RuntimeError: orphan field, expected ${getDebugName(parent)} to be an array`);
979
+ }
980
+ const data = parentValue[lastKnownKey];
981
+ if (isObject(data) && data.hasOwnProperty(parent.structure.identitySymbol) && data[parent.structure.identitySymbol] === identityInParent) {
982
+ return lastKnownKey;
983
+ }
984
+ for (let i = 0; i < parentValue.length; i++) {
985
+ const data = parentValue[i];
986
+ if (isObject(data) && data.hasOwnProperty(parent.structure.identitySymbol) && data[parent.structure.identitySymbol] === identityInParent) {
987
+ return lastKnownKey = i.toString();
988
+ }
989
+ }
990
+ throw new Error(`RuntimeError: orphan field, can't find element in array ${getDebugName(parent)}`);
991
+ }, ...(ngDevMode ? [{
992
+ debugName: "keyInParent"
993
+ }] : []));
994
+ }
1077
995
  this.value = deepSignal(this.parent.structure.value, this.keyInParent);
1078
- this.childrenMap = this.createChildrenMap();
996
+ this.childrenMap = makeChildrenMapSignal(node, this.value, this.identitySymbol, pathNode, logic, adapter, createChildNode);
1079
997
  this.fieldManager.structures.add(this);
1080
998
  }
1081
999
  }
@@ -1088,50 +1006,80 @@ const ROOT_KEY_IN_PARENT = computed(() => {
1088
1006
  }, ...(ngDevMode ? [{
1089
1007
  debugName: "ROOT_KEY_IN_PARENT"
1090
1008
  }] : []));
1009
+ function makeChildrenMapSignal(node, valueSignal, identitySymbol, pathNode, logic, adapter, createChildNode) {
1010
+ return linkedSignal({
1011
+ source: valueSignal,
1012
+ computation: (value, previous) => {
1013
+ let childrenMap = previous?.value;
1014
+ if (!isObject(value)) {
1015
+ return undefined;
1016
+ }
1017
+ const isValueArray = isArray(value);
1018
+ if (childrenMap !== undefined) {
1019
+ let oldKeys = undefined;
1020
+ if (isValueArray) {
1021
+ oldKeys = new Set(childrenMap.keys());
1022
+ for (let i = 0; i < value.length; i++) {
1023
+ const childValue = value[i];
1024
+ if (isObject(childValue) && childValue.hasOwnProperty(identitySymbol)) {
1025
+ oldKeys.delete(childValue[identitySymbol]);
1026
+ } else {
1027
+ oldKeys.delete(i.toString());
1028
+ }
1029
+ }
1030
+ for (const key of oldKeys) {
1031
+ childrenMap.delete(key);
1032
+ }
1033
+ } else {
1034
+ for (let key of childrenMap.keys()) {
1035
+ if (!value.hasOwnProperty(key)) {
1036
+ childrenMap.delete(key);
1037
+ }
1038
+ }
1039
+ }
1040
+ }
1041
+ for (let key of Object.keys(value)) {
1042
+ let trackingId = undefined;
1043
+ const childValue = value[key];
1044
+ if (childValue === undefined) {
1045
+ childrenMap?.delete(key);
1046
+ continue;
1047
+ }
1048
+ if (isValueArray && isObject(childValue) && !isArray(childValue)) {
1049
+ trackingId = childValue[identitySymbol] ??= Symbol(ngDevMode ? `id:${globalId++}` : '');
1050
+ }
1051
+ const identity = trackingId ?? key;
1052
+ if (childrenMap?.has(identity)) {
1053
+ continue;
1054
+ }
1055
+ let childPath;
1056
+ let childLogic;
1057
+ if (isValueArray) {
1058
+ childPath = pathNode.getChild(DYNAMIC);
1059
+ childLogic = logic.getChild(DYNAMIC);
1060
+ } else {
1061
+ childPath = pathNode.getChild(key);
1062
+ childLogic = logic.getChild(key);
1063
+ }
1064
+ childrenMap ??= new Map();
1065
+ childrenMap.set(identity, createChildNode({
1066
+ kind: 'child',
1067
+ parent: node,
1068
+ pathNode: childPath,
1069
+ logic: childLogic,
1070
+ initialKeyInParent: key,
1071
+ identityInParent: trackingId,
1072
+ fieldAdapter: adapter
1073
+ }));
1074
+ }
1075
+ return childrenMap;
1076
+ },
1077
+ equal: () => false
1078
+ });
1079
+ }
1091
1080
  function getDebugName(node) {
1092
1081
  return `<root>.${node.structure.pathKeys().join('.')}`;
1093
1082
  }
1094
- function maybeRemoveStaleArrayFields(prevData, value, identitySymbol) {
1095
- let data;
1096
- const oldKeys = new Set(prevData.byPropertyKey.keys());
1097
- const oldTracking = new Set(prevData.byTrackingKey?.keys());
1098
- for (let i = 0; i < value.length; i++) {
1099
- const childValue = value[i];
1100
- oldKeys.delete(i.toString());
1101
- if (isObject(childValue) && childValue.hasOwnProperty(identitySymbol)) {
1102
- oldTracking.delete(childValue[identitySymbol]);
1103
- }
1104
- }
1105
- if (oldKeys.size > 0) {
1106
- data ??= {
1107
- ...prevData
1108
- };
1109
- for (const key of oldKeys) {
1110
- data.byPropertyKey.delete(key);
1111
- }
1112
- }
1113
- if (oldTracking.size > 0) {
1114
- data ??= {
1115
- ...prevData
1116
- };
1117
- for (const id of oldTracking) {
1118
- data.byTrackingKey?.delete(id);
1119
- }
1120
- }
1121
- return data;
1122
- }
1123
- function maybeRemoveStaleObjectFields(prevData, value) {
1124
- let data;
1125
- for (const key of prevData.byPropertyKey.keys()) {
1126
- if (!value.hasOwnProperty(key)) {
1127
- data ??= {
1128
- ...prevData
1129
- };
1130
- data.byPropertyKey.delete(key);
1131
- }
1132
- }
1133
- return data;
1134
- }
1135
1083
 
1136
1084
  class FieldSubmitState {
1137
1085
  node;
@@ -1168,9 +1116,7 @@ class FieldNode {
1168
1116
  return this._context ??= new FieldNodeContext(this);
1169
1117
  }
1170
1118
  fieldProxy = new Proxy(() => this, FIELD_PROXY_HANDLER);
1171
- pathNode;
1172
1119
  constructor(options) {
1173
- this.pathNode = options.pathNode;
1174
1120
  this.fieldAdapter = options.fieldAdapter;
1175
1121
  this.structure = this.fieldAdapter.createStructure(this, options);
1176
1122
  this.validationState = this.fieldAdapter.createValidationState(this, options);
@@ -1245,23 +1191,26 @@ class FieldNode {
1245
1191
  get name() {
1246
1192
  return this.nodeState.name;
1247
1193
  }
1194
+ metadataOrUndefined(key) {
1195
+ return this.hasMetadata(key) ? this.metadata(key) : undefined;
1196
+ }
1248
1197
  get max() {
1249
- return this.metadata(MAX);
1198
+ return this.metadataOrUndefined(MAX);
1250
1199
  }
1251
1200
  get maxLength() {
1252
- return this.metadata(MAX_LENGTH);
1201
+ return this.metadataOrUndefined(MAX_LENGTH);
1253
1202
  }
1254
1203
  get min() {
1255
- return this.metadata(MIN);
1204
+ return this.metadataOrUndefined(MIN);
1256
1205
  }
1257
1206
  get minLength() {
1258
- return this.metadata(MIN_LENGTH);
1207
+ return this.metadataOrUndefined(MIN_LENGTH);
1259
1208
  }
1260
1209
  get pattern() {
1261
- return this.metadata(PATTERN) ?? EMPTY;
1210
+ return this.metadataOrUndefined(PATTERN) ?? EMPTY;
1262
1211
  }
1263
1212
  get required() {
1264
- return this.metadata(REQUIRED) ?? FALSE;
1213
+ return this.metadataOrUndefined(REQUIRED) ?? FALSE;
1265
1214
  }
1266
1215
  metadata(key) {
1267
1216
  return this.metadataState.get(key);
@@ -1281,7 +1230,7 @@ class FieldNode {
1281
1230
  untracked(() => this._reset(value));
1282
1231
  }
1283
1232
  _reset(value) {
1284
- if (value !== undefined) {
1233
+ if (value) {
1285
1234
  this.value.set(value);
1286
1235
  }
1287
1236
  this.nodeState.markAsUntouched();
@@ -1317,28 +1266,11 @@ class FieldNode {
1317
1266
  static newRoot(fieldManager, value, pathNode, adapter) {
1318
1267
  return adapter.newRoot(fieldManager, value, pathNode, adapter);
1319
1268
  }
1269
+ static newChild(options) {
1270
+ return options.fieldAdapter.newChild(options);
1271
+ }
1320
1272
  createStructure(options) {
1321
- return options.kind === 'root' ? new RootFieldNodeStructure(this, options.logic, options.fieldManager, options.value, this.newChild.bind(this)) : new ChildFieldNodeStructure(this, options.logic, options.parent, options.identityInParent, options.initialKeyInParent, this.newChild.bind(this));
1322
- }
1323
- newChild(key, trackingId, isArray) {
1324
- let childPath;
1325
- let childLogic;
1326
- if (isArray) {
1327
- childPath = this.pathNode.getChild(DYNAMIC);
1328
- childLogic = this.structure.logic.getChild(DYNAMIC);
1329
- } else {
1330
- childPath = this.pathNode.getChild(key);
1331
- childLogic = this.structure.logic.getChild(key);
1332
- }
1333
- return this.fieldAdapter.newChild({
1334
- kind: 'child',
1335
- parent: this,
1336
- pathNode: childPath,
1337
- logic: childLogic,
1338
- initialKeyInParent: key,
1339
- identityInParent: trackingId,
1340
- fieldAdapter: this.fieldAdapter
1341
- });
1273
+ return options.kind === 'root' ? new RootFieldNodeStructure(this, options.pathNode, options.logic, options.fieldManager, options.value, options.fieldAdapter, FieldNode.newChild) : new ChildFieldNodeStructure(this, options.pathNode, options.logic, options.parent, options.identityInParent, options.initialKeyInParent, options.fieldAdapter, FieldNode.newChild);
1342
1274
  }
1343
1275
  }
1344
1276
  const EMPTY = computed(() => [], ...(ngDevMode ? [{
@@ -1376,13 +1308,13 @@ class FieldNodeState {
1376
1308
  }
1377
1309
  dirty = computed(() => {
1378
1310
  const selfDirtyValue = this.selfDirty() && !this.isNonInteractive();
1379
- return this.node.structure.reduceChildren(selfDirtyValue, (child, value) => value || child.nodeState.dirty(), shortCircuitTrue);
1311
+ return reduceChildren(this.node, selfDirtyValue, (child, value) => value || child.nodeState.dirty(), shortCircuitTrue);
1380
1312
  }, ...(ngDevMode ? [{
1381
1313
  debugName: "dirty"
1382
1314
  }] : []));
1383
1315
  touched = computed(() => {
1384
1316
  const selfTouchedValue = this.selfTouched() && !this.isNonInteractive();
1385
- return this.node.structure.reduceChildren(selfTouchedValue, (child, value) => value || child.nodeState.touched(), shortCircuitTrue);
1317
+ return reduceChildren(this.node, selfTouchedValue, (child, value) => value || child.nodeState.touched(), shortCircuitTrue);
1386
1318
  }, ...(ngDevMode ? [{
1387
1319
  debugName: "touched"
1388
1320
  }] : []));
@@ -1408,8 +1340,8 @@ class FieldNodeState {
1408
1340
  debugName: "name"
1409
1341
  }] : []));
1410
1342
  debouncer = computed(() => {
1411
- if (this.node.logicNode.logic.hasMetadata(DEBOUNCER)) {
1412
- const debouncerLogic = this.node.logicNode.logic.getMetadata(DEBOUNCER);
1343
+ if (this.node.logicNode.logic.hasAggregateMetadata(DEBOUNCER)) {
1344
+ const debouncerLogic = this.node.logicNode.logic.getAggregateMetadata(DEBOUNCER);
1413
1345
  const debouncer = debouncerLogic.compute(this.node.context);
1414
1346
  if (debouncer) {
1415
1347
  return signal => debouncer(this.node.context, signal);
@@ -1574,5 +1506,5 @@ function markAllAsTouched(node) {
1574
1506
  }
1575
1507
  }
1576
1508
 
1577
- export { BasicFieldAdapter, DEBOUNCER, FieldNode, FieldNodeState, FieldNodeStructure, FieldPathNode, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MetadataKey, MetadataReducer, PATTERN, REQUIRED, addDefaultField, apply, applyEach, applyWhen, applyWhenValue, assertPathIsCurrent, calculateValidationSelfStatus, createManagedMetadataKey, createMetadataKey, form, getInjectorFromOptions, isArray, metadata, normalizeFormArgs, schema, submit };
1509
+ export { AggregateMetadataKey, BasicFieldAdapter, DEBOUNCER, FieldNode, FieldNodeState, FieldNodeStructure, FieldPathNode, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MetadataKey, PATTERN, REQUIRED, addDefaultField, andMetadataKey, apply, applyEach, applyWhen, applyWhenValue, assertPathIsCurrent, calculateValidationSelfStatus, createMetadataKey, form, getInjectorFromOptions, isArray, listMetadataKey, maxMetadataKey, minMetadataKey, normalizeFormArgs, orMetadataKey, reducedMetadataKey, schema, submit };
1578
1510
  //# sourceMappingURL=_structure-chunk.mjs.map