@angular/forms 21.1.0-next.1 → 21.1.0-next.2

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,34 +1,28 @@
1
1
  /**
2
- * @license Angular v21.1.0-next.1
2
+ * @license Angular v21.1.0-next.2
3
3
  * (c) 2010-2025 Google LLC. https://angular.dev/
4
4
  * License: MIT
5
5
  */
6
6
 
7
- import { computed, untracked, runInInjectionContext, linkedSignal, Injector, signal, APP_ID, effect, inject } from '@angular/core';
7
+ import { untracked, computed, runInInjectionContext, Injector, linkedSignal, 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
- function isArray(value) {
12
- return Array.isArray(value);
13
- }
14
- function isObject(value) {
15
- return (typeof value === 'object' || typeof value === 'function') && value != null;
11
+ let boundPathDepth = 0;
12
+ function getBoundPathDepth() {
13
+ return boundPathDepth;
16
14
  }
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;
15
+ function setBoundPathDepthForResolution(fn, depth) {
16
+ return (...args) => {
17
+ try {
18
+ boundPathDepth = depth;
19
+ return fn(...args);
20
+ } finally {
21
+ boundPathDepth = 0;
27
22
  }
28
- value = fn(child, value);
29
- }
30
- return value;
23
+ };
31
24
  }
25
+
32
26
  function shortCircuitFalse(value) {
33
27
  return !value;
34
28
  }
@@ -42,131 +36,11 @@ function getInjectorFromOptions(options) {
42
36
  return options.parent.structure.root.structure.injector;
43
37
  }
44
38
 
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';
53
- }
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;
39
+ function isArray(value) {
40
+ return Array.isArray(value);
160
41
  }
161
- function setBoundPathDepthForResolution(fn, depth) {
162
- return (...args) => {
163
- try {
164
- boundPathDepth = depth;
165
- return fn(...args);
166
- } finally {
167
- boundPathDepth = 0;
168
- }
169
- };
42
+ function isObject(value) {
43
+ return (typeof value === 'object' || typeof value === 'function') && value != null;
170
44
  }
171
45
 
172
46
  const DYNAMIC = Symbol();
@@ -229,10 +103,10 @@ class ArrayMergeLogic extends ArrayMergeIgnoreLogic {
229
103
  super(predicates, undefined);
230
104
  }
231
105
  }
232
- class AggregateMetadataMergeLogic extends AbstractLogic {
106
+ class MetadataMergeLogic extends AbstractLogic {
233
107
  key;
234
108
  get defaultValue() {
235
- return this.key.getInitial();
109
+ return this.key.reducer.getInitial();
236
110
  }
237
111
  constructor(predicates, key) {
238
112
  super(predicates);
@@ -240,13 +114,13 @@ class AggregateMetadataMergeLogic extends AbstractLogic {
240
114
  }
241
115
  compute(ctx) {
242
116
  if (this.fns.length === 0) {
243
- return this.key.getInitial();
117
+ return this.key.reducer.getInitial();
244
118
  }
245
- let acc = this.key.getInitial();
119
+ let acc = this.key.reducer.getInitial();
246
120
  for (let i = 0; i < this.fns.length; i++) {
247
121
  const item = this.fns[i](ctx);
248
122
  if (item !== IGNORED) {
249
- acc = this.key.reduce(acc, item);
123
+ acc = this.key.reducer.reduce(acc, item);
250
124
  }
251
125
  }
252
126
  return acc;
@@ -278,8 +152,7 @@ class LogicContainer {
278
152
  syncErrors;
279
153
  syncTreeErrors;
280
154
  asyncErrors;
281
- aggregateMetadataKeys = new Map();
282
- metadataFactories = new Map();
155
+ metadata = new Map();
283
156
  constructor(predicates) {
284
157
  this.predicates = predicates;
285
158
  this.hidden = new BooleanOrLogic(predicates);
@@ -289,26 +162,17 @@ class LogicContainer {
289
162
  this.syncTreeErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
290
163
  this.asyncErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
291
164
  }
292
- hasAggregateMetadata(key) {
293
- return this.aggregateMetadataKeys.has(key);
294
- }
295
- getAggregateMetadataEntries() {
296
- return this.aggregateMetadataKeys.entries();
297
- }
298
- getMetadataFactoryEntries() {
299
- return this.metadataFactories.entries();
165
+ hasMetadata(key) {
166
+ return this.metadata.has(key);
300
167
  }
301
- getAggregateMetadata(key) {
302
- if (!this.aggregateMetadataKeys.has(key)) {
303
- this.aggregateMetadataKeys.set(key, new AggregateMetadataMergeLogic(this.predicates, key));
304
- }
305
- return this.aggregateMetadataKeys.get(key);
168
+ getMetadataKeys() {
169
+ return this.metadata.keys();
306
170
  }
307
- addMetadataFactory(key, factory) {
308
- if (this.metadataFactories.has(key)) {
309
- throw new Error(`Can't define value twice for the same MetadataKey`);
171
+ getMetadata(key) {
172
+ if (!this.metadata.has(key)) {
173
+ this.metadata.set(key, new MetadataMergeLogic(this.predicates, key));
310
174
  }
311
- this.metadataFactories.set(key, factory);
175
+ return this.metadata.get(key);
312
176
  }
313
177
  mergeIn(other) {
314
178
  this.hidden.mergeIn(other.hidden);
@@ -317,11 +181,9 @@ class LogicContainer {
317
181
  this.syncErrors.mergeIn(other.syncErrors);
318
182
  this.syncTreeErrors.mergeIn(other.syncTreeErrors);
319
183
  this.asyncErrors.mergeIn(other.asyncErrors);
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);
184
+ for (const key of other.getMetadataKeys()) {
185
+ const metadataLogic = other.metadata.get(key);
186
+ this.getMetadata(key).mergeIn(metadataLogic);
325
187
  }
326
188
  }
327
189
  }
@@ -359,11 +221,8 @@ class LogicNodeBuilder extends AbstractLogicNodeBuilder {
359
221
  addAsyncErrorRule(logic) {
360
222
  this.getCurrent().addAsyncErrorRule(logic);
361
223
  }
362
- addAggregateMetadataRule(key, logic) {
363
- this.getCurrent().addAggregateMetadataRule(key, logic);
364
- }
365
- addMetadataFactory(key, factory) {
366
- this.getCurrent().addMetadataFactory(key, factory);
224
+ addMetadataRule(key, logic) {
225
+ this.getCurrent().addMetadataRule(key, logic);
367
226
  }
368
227
  getChild(key) {
369
228
  if (key === DYNAMIC) {
@@ -435,11 +294,8 @@ class NonMergeableLogicNodeBuilder extends AbstractLogicNodeBuilder {
435
294
  addAsyncErrorRule(logic) {
436
295
  this.logic.asyncErrors.push(setBoundPathDepthForResolution(logic, this.depth));
437
296
  }
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));
297
+ addMetadataRule(key, logic) {
298
+ this.logic.getMetadata(key).push(setBoundPathDepthForResolution(logic, this.depth));
443
299
  }
444
300
  getChild(key) {
445
301
  if (!this.children.has(key)) {
@@ -657,64 +513,196 @@ function assertPathIsCurrent(path) {
657
513
  }
658
514
  }
659
515
 
660
- class MetadataKey {
661
- brand;
662
- constructor() {}
516
+ function metadata(path, key, logic) {
517
+ assertPathIsCurrent(path);
518
+ const pathNode = FieldPathNode.unwrapFieldPath(path);
519
+ pathNode.builder.addMetadataRule(key, logic);
520
+ return key;
663
521
  }
664
- function createMetadataKey() {
665
- return new MetadataKey();
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
+ };
666
570
  }
667
- class AggregateMetadataKey {
668
- reduce;
669
- getInitial;
571
+ class MetadataKey {
572
+ reducer;
573
+ create;
670
574
  brand;
671
- constructor(reduce, getInitial) {
672
- this.reduce = reduce;
673
- this.getInitial = getInitial;
575
+ constructor(reducer, create) {
576
+ this.reducer = reducer;
577
+ this.create = create;
674
578
  }
675
579
  }
676
- function reducedMetadataKey(reduce, getInitial) {
677
- return new AggregateMetadataKey(reduce, getInitial);
580
+ function createMetadataKey(reducer) {
581
+ return new MetadataKey(reducer ?? MetadataReducer.override());
678
582
  }
679
- function listMetadataKey() {
680
- return reducedMetadataKey((acc, item) => item === undefined ? acc : [...acc, item], () => []);
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';
681
601
  }
682
- function minMetadataKey() {
683
- return reducedMetadataKey((prev, next) => {
684
- if (prev === undefined) {
685
- return next;
602
+ class FieldValidationState {
603
+ node;
604
+ constructor(node) {
605
+ this.node = node;
606
+ }
607
+ rawSyncTreeErrors = computed(() => {
608
+ if (this.shouldSkipValidation()) {
609
+ return [];
686
610
  }
687
- if (next === undefined) {
688
- return prev;
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 [];
689
618
  }
690
- return Math.min(prev, next);
691
- }, () => undefined);
692
- }
693
- function maxMetadataKey() {
694
- return reducedMetadataKey((prev, next) => {
695
- if (prev === undefined) {
696
- return next;
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;
697
626
  }
698
- if (next === undefined) {
699
- return prev;
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 [];
700
637
  }
701
- return Math.max(prev, next);
702
- }, () => undefined);
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 [];
645
+ }
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';
662
+ }
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
+ }] : []));
703
684
  }
704
- function orMetadataKey() {
705
- return reducedMetadataKey((prev, next) => prev || next, () => false);
685
+ function normalizeErrors(error) {
686
+ if (error === undefined) {
687
+ return [];
688
+ }
689
+ if (isArray(error)) {
690
+ return error;
691
+ }
692
+ return [error];
706
693
  }
707
- function andMetadataKey() {
708
- return reducedMetadataKey((prev, next) => prev && next, () => true);
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;
709
703
  }
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();
716
704
 
717
- const DEBOUNCER = reducedMetadataKey((_, item) => item, () => undefined);
705
+ const DEBOUNCER = createMetadataKey();
718
706
 
719
707
  class FieldNodeContext {
720
708
  node;
@@ -789,31 +777,28 @@ class FieldMetadataState {
789
777
  metadata = new Map();
790
778
  constructor(node) {
791
779
  this.node = node;
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));
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);
795
785
  }
796
- }));
786
+ }
797
787
  }
798
788
  get(key) {
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);
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
+ }
808
797
  }
809
798
  return this.metadata.get(key);
810
799
  }
811
800
  has(key) {
812
- if (key instanceof AggregateMetadataKey) {
813
- return this.node.logicNode.logic.hasAggregateMetadata(key);
814
- } else {
815
- return this.metadata.has(key);
816
- }
801
+ return this.node.logicNode.logic.hasMetadata(key);
817
802
  }
818
803
  }
819
804
 
@@ -830,7 +815,10 @@ const FIELD_PROXY_HANDLER = {
830
815
  return tgt.value().length;
831
816
  }
832
817
  if (p === Symbol.iterator) {
833
- return Array.prototype[p];
818
+ return () => {
819
+ tgt.value();
820
+ return Array.prototype[Symbol.iterator].apply(tgt.fieldProxy);
821
+ };
834
822
  }
835
823
  }
836
824
  if (isObject(value)) {
@@ -885,6 +873,8 @@ function valueForWrite(sourceValue, newPropValue, prop) {
885
873
 
886
874
  class FieldNodeStructure {
887
875
  logic;
876
+ node;
877
+ createChildNode;
888
878
  identitySymbol = Symbol();
889
879
  _injector = undefined;
890
880
  get injector() {
@@ -894,32 +884,146 @@ class FieldNodeStructure {
894
884
  });
895
885
  return this._injector;
896
886
  }
897
- constructor(logic) {
887
+ constructor(logic, node, createChildNode) {
898
888
  this.logic = logic;
889
+ this.node = node;
890
+ this.createChildNode = createChildNode;
899
891
  }
900
892
  children() {
901
- return this.childrenMap()?.values() ?? [];
893
+ const map = this.childrenMap();
894
+ if (map === undefined) {
895
+ return [];
896
+ }
897
+ return Array.from(map.byPropertyKey.values()).map(child => untracked(child.reader));
902
898
  }
903
899
  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) {
904
908
  const map = this.childrenMap();
905
- const value = this.value();
906
- if (!map || !isObject(value)) {
907
- return undefined;
909
+ if (!map) {
910
+ return initialValue;
908
911
  }
909
- if (isArray(value)) {
910
- const childValue = value[key];
911
- if (isObject(childValue) && childValue.hasOwnProperty(this.identitySymbol)) {
912
- key = childValue[this.identitySymbol];
912
+ let value = initialValue;
913
+ for (const child of map.byPropertyKey.values()) {
914
+ if (shortCircuit?.(value)) {
915
+ break;
913
916
  }
917
+ value = fn(untracked(child.reader), value);
914
918
  }
915
- return map.get(typeof key === 'number' ? key.toString() : key);
919
+ return value;
916
920
  }
917
921
  destroy() {
918
922
  this.injector.destroy();
919
923
  }
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
+ }
920
1025
  }
921
1026
  class RootFieldNodeStructure extends FieldNodeStructure {
922
- node;
923
1027
  fieldManager;
924
1028
  value;
925
1029
  get parent() {
@@ -935,15 +1039,15 @@ class RootFieldNodeStructure extends FieldNodeStructure {
935
1039
  return ROOT_KEY_IN_PARENT;
936
1040
  }
937
1041
  childrenMap;
938
- constructor(node, pathNode, logic, fieldManager, value, adapter, createChildNode) {
939
- super(logic);
940
- this.node = node;
1042
+ constructor(node, logic, fieldManager, value, createChildNode) {
1043
+ super(logic, node, createChildNode);
941
1044
  this.fieldManager = fieldManager;
942
1045
  this.value = value;
943
- this.childrenMap = makeChildrenMapSignal(node, value, this.identitySymbol, pathNode, logic, adapter, createChildNode);
1046
+ this.childrenMap = this.createChildrenMap();
944
1047
  }
945
1048
  }
946
1049
  class ChildFieldNodeStructure extends FieldNodeStructure {
1050
+ logic;
947
1051
  parent;
948
1052
  root;
949
1053
  pathKeys;
@@ -953,47 +1057,25 @@ class ChildFieldNodeStructure extends FieldNodeStructure {
953
1057
  get fieldManager() {
954
1058
  return this.root.structure.fieldManager;
955
1059
  }
956
- constructor(node, pathNode, logic, parent, identityInParent, initialKeyInParent, adapter, createChildNode) {
957
- super(logic);
1060
+ constructor(node, logic, parent, identityInParent, initialKeyInParent, createChildNode) {
1061
+ super(logic, node, createChildNode);
1062
+ this.logic = logic;
958
1063
  this.parent = parent;
959
1064
  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);
960
1074
  this.pathKeys = computed(() => [...parent.structure.pathKeys(), this.keyInParent()], ...(ngDevMode ? [{
961
1075
  debugName: "pathKeys"
962
1076
  }] : []));
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
- }
995
1077
  this.value = deepSignal(this.parent.structure.value, this.keyInParent);
996
- this.childrenMap = makeChildrenMapSignal(node, this.value, this.identitySymbol, pathNode, logic, adapter, createChildNode);
1078
+ this.childrenMap = this.createChildrenMap();
997
1079
  this.fieldManager.structures.add(this);
998
1080
  }
999
1081
  }
@@ -1006,80 +1088,50 @@ const ROOT_KEY_IN_PARENT = computed(() => {
1006
1088
  }, ...(ngDevMode ? [{
1007
1089
  debugName: "ROOT_KEY_IN_PARENT"
1008
1090
  }] : []));
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
- }
1080
1091
  function getDebugName(node) {
1081
1092
  return `<root>.${node.structure.pathKeys().join('.')}`;
1082
1093
  }
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
+ }
1083
1135
 
1084
1136
  class FieldSubmitState {
1085
1137
  node;
@@ -1116,7 +1168,9 @@ class FieldNode {
1116
1168
  return this._context ??= new FieldNodeContext(this);
1117
1169
  }
1118
1170
  fieldProxy = new Proxy(() => this, FIELD_PROXY_HANDLER);
1171
+ pathNode;
1119
1172
  constructor(options) {
1173
+ this.pathNode = options.pathNode;
1120
1174
  this.fieldAdapter = options.fieldAdapter;
1121
1175
  this.structure = this.fieldAdapter.createStructure(this, options);
1122
1176
  this.validationState = this.fieldAdapter.createValidationState(this, options);
@@ -1191,26 +1245,23 @@ class FieldNode {
1191
1245
  get name() {
1192
1246
  return this.nodeState.name;
1193
1247
  }
1194
- metadataOrUndefined(key) {
1195
- return this.hasMetadata(key) ? this.metadata(key) : undefined;
1196
- }
1197
1248
  get max() {
1198
- return this.metadataOrUndefined(MAX);
1249
+ return this.metadata(MAX);
1199
1250
  }
1200
1251
  get maxLength() {
1201
- return this.metadataOrUndefined(MAX_LENGTH);
1252
+ return this.metadata(MAX_LENGTH);
1202
1253
  }
1203
1254
  get min() {
1204
- return this.metadataOrUndefined(MIN);
1255
+ return this.metadata(MIN);
1205
1256
  }
1206
1257
  get minLength() {
1207
- return this.metadataOrUndefined(MIN_LENGTH);
1258
+ return this.metadata(MIN_LENGTH);
1208
1259
  }
1209
1260
  get pattern() {
1210
- return this.metadataOrUndefined(PATTERN) ?? EMPTY;
1261
+ return this.metadata(PATTERN) ?? EMPTY;
1211
1262
  }
1212
1263
  get required() {
1213
- return this.metadataOrUndefined(REQUIRED) ?? FALSE;
1264
+ return this.metadata(REQUIRED) ?? FALSE;
1214
1265
  }
1215
1266
  metadata(key) {
1216
1267
  return this.metadataState.get(key);
@@ -1230,7 +1281,7 @@ class FieldNode {
1230
1281
  untracked(() => this._reset(value));
1231
1282
  }
1232
1283
  _reset(value) {
1233
- if (value) {
1284
+ if (value !== undefined) {
1234
1285
  this.value.set(value);
1235
1286
  }
1236
1287
  this.nodeState.markAsUntouched();
@@ -1266,11 +1317,28 @@ class FieldNode {
1266
1317
  static newRoot(fieldManager, value, pathNode, adapter) {
1267
1318
  return adapter.newRoot(fieldManager, value, pathNode, adapter);
1268
1319
  }
1269
- static newChild(options) {
1270
- return options.fieldAdapter.newChild(options);
1271
- }
1272
1320
  createStructure(options) {
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);
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
+ });
1274
1342
  }
1275
1343
  }
1276
1344
  const EMPTY = computed(() => [], ...(ngDevMode ? [{
@@ -1308,13 +1376,13 @@ class FieldNodeState {
1308
1376
  }
1309
1377
  dirty = computed(() => {
1310
1378
  const selfDirtyValue = this.selfDirty() && !this.isNonInteractive();
1311
- return reduceChildren(this.node, selfDirtyValue, (child, value) => value || child.nodeState.dirty(), shortCircuitTrue);
1379
+ return this.node.structure.reduceChildren(selfDirtyValue, (child, value) => value || child.nodeState.dirty(), shortCircuitTrue);
1312
1380
  }, ...(ngDevMode ? [{
1313
1381
  debugName: "dirty"
1314
1382
  }] : []));
1315
1383
  touched = computed(() => {
1316
1384
  const selfTouchedValue = this.selfTouched() && !this.isNonInteractive();
1317
- return reduceChildren(this.node, selfTouchedValue, (child, value) => value || child.nodeState.touched(), shortCircuitTrue);
1385
+ return this.node.structure.reduceChildren(selfTouchedValue, (child, value) => value || child.nodeState.touched(), shortCircuitTrue);
1318
1386
  }, ...(ngDevMode ? [{
1319
1387
  debugName: "touched"
1320
1388
  }] : []));
@@ -1340,8 +1408,8 @@ class FieldNodeState {
1340
1408
  debugName: "name"
1341
1409
  }] : []));
1342
1410
  debouncer = computed(() => {
1343
- if (this.node.logicNode.logic.hasAggregateMetadata(DEBOUNCER)) {
1344
- const debouncerLogic = this.node.logicNode.logic.getAggregateMetadata(DEBOUNCER);
1411
+ if (this.node.logicNode.logic.hasMetadata(DEBOUNCER)) {
1412
+ const debouncerLogic = this.node.logicNode.logic.getMetadata(DEBOUNCER);
1345
1413
  const debouncer = debouncerLogic.compute(this.node.context);
1346
1414
  if (debouncer) {
1347
1415
  return signal => debouncer(this.node.context, signal);
@@ -1506,5 +1574,5 @@ function markAllAsTouched(node) {
1506
1574
  }
1507
1575
  }
1508
1576
 
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 };
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 };
1510
1578
  //# sourceMappingURL=_structure-chunk.mjs.map