@signaltree/ng-forms 7.3.4 → 7.6.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,895 +0,0 @@
1
- import { __decorate, __metadata } from '../tslib.es6.js';
2
- import { Input, Output, HostListener, Directive, forwardRef, EventEmitter, inject, ElementRef, Renderer2, effect, signal, computed, isSignal, DestroyRef } from '@angular/core';
3
- import { NG_VALUE_ACCESSOR, FormControl, FormArray, FormGroup } from '@angular/forms';
4
- import { signalTree } from '@signaltree/core';
5
- import { isObservable, firstValueFrom } from 'rxjs';
6
- import { deepClone } from '../deep-clone.js';
7
- import { mergeDeep } from '../merge-deep.js';
8
- import { parsePath } from '../parse-path.js';
9
- import { matchPath } from '../match-path.js';
10
-
11
- function isDevEnvironment() {
12
- if (typeof __DEV__ !== 'undefined') return __DEV__;
13
- if (typeof ngDevMode !== 'undefined') return Boolean(ngDevMode);
14
- return true;
15
- }
16
- let hasShownLegacyWarning = false;
17
- class FormValidationError extends Error {
18
- errors;
19
- asyncErrors;
20
- constructor(errors, asyncErrors) {
21
- super('Form validation failed');
22
- this.errors = errors;
23
- this.asyncErrors = asyncErrors;
24
- this.name = 'FormValidationError';
25
- }
26
- }
27
- const SYNC_ERROR_KEY = 'signaltree';
28
- const ASYNC_ERROR_KEY = 'signaltreeAsync';
29
- let hasShownCreateFormTreeDeprecation = false;
30
- function createFormTree(initialValues, config = {}) {
31
- if (isDevEnvironment() && !hasShownCreateFormTreeDeprecation) {
32
- hasShownCreateFormTreeDeprecation = true;
33
- console.warn(`[@signaltree/ng-forms] createFormTree() is deprecated. ` + `Use signalTree({ myForm: form({...}) }).with(formBridge()) instead. ` + `See https://signaltree.dev/migration/ng-forms for migration guide.`);
34
- }
35
- const {
36
- validators: baseValidators = {},
37
- asyncValidators: baseAsyncValidators = {},
38
- destroyRef: providedDestroyRef,
39
- fieldConfigs = {},
40
- conditionals = [],
41
- persistKey,
42
- storage,
43
- persistDebounceMs = 100,
44
- validationBatchMs = 0,
45
- ...treeConfig
46
- } = config;
47
- const syncValidators = normalizeSyncValidators(baseValidators, fieldConfigs);
48
- const asyncValidators = normalizeAsyncValidators(baseAsyncValidators, fieldConfigs);
49
- const {
50
- values: hydratedInitialValues
51
- } = hydrateInitialValues(initialValues, persistKey, storage);
52
- const initialSnapshot = deepClone(hydratedInitialValues);
53
- const valuesTree = signalTree(hydratedInitialValues, treeConfig);
54
- assertTreeNode(valuesTree.state);
55
- const flattenedState = valuesTree.state;
56
- enhanceArraysRecursively(flattenedState);
57
- const formGroup = createAbstractControl(hydratedInitialValues, '', syncValidators, asyncValidators);
58
- const destroyRef = providedDestroyRef ?? tryInjectDestroyRef();
59
- const cleanupCallbacks = [];
60
- const errors = signal({});
61
- const asyncErrors = signal({});
62
- const touched = signal({});
63
- const asyncValidating = signal({});
64
- const dirty = signal(formGroup.dirty);
65
- const valid = signal(formGroup.valid && !formGroup.pending);
66
- const submitting = signal(false);
67
- const refreshRunner = () => {
68
- const snapshot = collectControlSnapshot(formGroup);
69
- errors.set(snapshot.syncErrors);
70
- asyncErrors.set(snapshot.asyncErrors);
71
- touched.set(snapshot.touched);
72
- asyncValidating.set(snapshot.pending);
73
- dirty.set(formGroup.dirty);
74
- valid.set(formGroup.valid && !formGroup.pending);
75
- };
76
- let refreshTimer = null;
77
- const refreshAggregates = (immediate = false) => {
78
- if (immediate || validationBatchMs <= 0) {
79
- if (refreshTimer) {
80
- clearTimeout(refreshTimer);
81
- refreshTimer = null;
82
- }
83
- refreshRunner();
84
- return;
85
- }
86
- if (refreshTimer) {
87
- clearTimeout(refreshTimer);
88
- }
89
- refreshTimer = setTimeout(() => {
90
- refreshTimer = null;
91
- refreshRunner();
92
- }, validationBatchMs);
93
- };
94
- refreshAggregates(true);
95
- const persistController = createPersistController(formGroup, persistKey, storage, persistDebounceMs, cleanupCallbacks);
96
- const fieldErrorKeys = new Set([...Object.keys(syncValidators), ...Object.keys(asyncValidators)]);
97
- const fieldErrors = {};
98
- const fieldAsyncErrors = {};
99
- fieldErrorKeys.forEach(fieldPath => {
100
- fieldErrors[fieldPath] = computed(() => errors()[fieldPath]);
101
- fieldAsyncErrors[fieldPath] = computed(() => asyncErrors()[fieldPath]);
102
- });
103
- const fieldConfigLookup = path => resolveFieldConfig(fieldConfigs, path);
104
- const connectControlRecursive = (control, path) => {
105
- if (control instanceof FormGroup) {
106
- Object.entries(control.controls).forEach(([key, child]) => {
107
- connectControlRecursive(child, joinPath(path, key));
108
- });
109
- return;
110
- }
111
- if (control instanceof FormArray) {
112
- const arraySignal = getSignalAtPath(flattenedState, path);
113
- if (arraySignal) {
114
- connectFormArrayAndSignal(control, arraySignal, path, syncValidators, asyncValidators, cleanupCallbacks, connectControlRecursive);
115
- }
116
- control.controls.forEach((child, index) => {
117
- connectControlRecursive(child, joinPath(path, String(index)));
118
- });
119
- return;
120
- }
121
- const signalAtPath = getSignalAtPath(flattenedState, path);
122
- if (signalAtPath) {
123
- connectControlAndSignal(control, signalAtPath, cleanupCallbacks, fieldConfigLookup(path));
124
- }
125
- };
126
- connectControlRecursive(formGroup, '');
127
- const conditionalState = new Map();
128
- const applyConditionals = conditionals.length > 0 ? () => {
129
- const values = formGroup.getRawValue();
130
- conditionals.forEach(({
131
- when,
132
- fields
133
- }) => {
134
- let visible = true;
135
- try {
136
- visible = when(values);
137
- } catch {
138
- visible = true;
139
- }
140
- fields.forEach(fieldPath => {
141
- const control = formGroup.get(fieldPath);
142
- if (!control) {
143
- return;
144
- }
145
- const previous = conditionalState.get(fieldPath);
146
- if (previous === visible) {
147
- return;
148
- }
149
- conditionalState.set(fieldPath, visible);
150
- if (visible) {
151
- const signalAtPath = getSignalAtPath(flattenedState, fieldPath);
152
- if (signalAtPath && 'set' in signalAtPath) {
153
- signalAtPath.set(control.value);
154
- }
155
- control.enable({
156
- emitEvent: true
157
- });
158
- } else {
159
- control.disable({
160
- emitEvent: false
161
- });
162
- }
163
- });
164
- });
165
- } : () => undefined;
166
- applyConditionals();
167
- const aggregateSubscriptions = [];
168
- aggregateSubscriptions.push(formGroup.valueChanges.subscribe(() => {
169
- refreshAggregates();
170
- persistController.schedulePersist();
171
- applyConditionals();
172
- }));
173
- aggregateSubscriptions.push(formGroup.statusChanges.subscribe(() => refreshAggregates()));
174
- if (formGroup.events) {
175
- aggregateSubscriptions.push(formGroup.events.subscribe(() => refreshAggregates()));
176
- }
177
- cleanupCallbacks.push(() => {
178
- aggregateSubscriptions.forEach(sub => sub.unsubscribe());
179
- if (refreshTimer) {
180
- clearTimeout(refreshTimer);
181
- }
182
- });
183
- if (destroyRef) {
184
- destroyRef.onDestroy(() => {
185
- cleanupCallbacks.splice(0).forEach(fn => fn());
186
- });
187
- }
188
- const setValue = (field, value) => {
189
- const targetSignal = getSignalAtPath(flattenedState, field);
190
- const control = formGroup.get(field);
191
- if (targetSignal && 'set' in targetSignal) {
192
- targetSignal.set(value);
193
- } else if (control) {
194
- control.setValue(value, {
195
- emitEvent: true
196
- });
197
- }
198
- if (control) {
199
- const untypedControl = control;
200
- untypedControl.markAsTouched();
201
- untypedControl.markAsDirty();
202
- untypedControl.updateValueAndValidity({
203
- emitEvent: true
204
- });
205
- }
206
- refreshAggregates(true);
207
- persistController.schedulePersist();
208
- };
209
- const setValues = values => {
210
- Object.entries(values).forEach(([key, value]) => {
211
- setValue(key, value);
212
- });
213
- };
214
- const reset = () => {
215
- formGroup.reset(initialSnapshot);
216
- submitting.set(false);
217
- refreshAggregates(true);
218
- persistController.persistImmediately();
219
- applyConditionals();
220
- };
221
- const validate = async field => {
222
- if (field) {
223
- const control = formGroup.get(field);
224
- if (!control) {
225
- return;
226
- }
227
- control.markAsTouched();
228
- control.updateValueAndValidity({
229
- emitEvent: true
230
- });
231
- refreshAggregates(true);
232
- await waitForPending(control);
233
- refreshAggregates(true);
234
- return;
235
- }
236
- formGroup.markAllAsTouched();
237
- formGroup.updateValueAndValidity({
238
- emitEvent: true
239
- });
240
- refreshAggregates(true);
241
- await waitForPending(formGroup);
242
- refreshAggregates(true);
243
- };
244
- const submit = async submitFn => {
245
- submitting.set(true);
246
- try {
247
- await validate();
248
- if (!valid()) {
249
- throw new FormValidationError(errors(), asyncErrors());
250
- }
251
- const currentValues = formGroup.getRawValue();
252
- const result = await submitFn(currentValues);
253
- return result;
254
- } finally {
255
- submitting.set(false);
256
- refreshAggregates(true);
257
- }
258
- };
259
- const destroy = () => {
260
- cleanupCallbacks.splice(0).forEach(fn => fn());
261
- };
262
- const formTree = {
263
- state: flattenedState,
264
- $: flattenedState,
265
- form: formGroup,
266
- errors,
267
- asyncErrors,
268
- touched,
269
- asyncValidating,
270
- dirty,
271
- valid,
272
- submitting,
273
- unwrap: () => valuesTree(),
274
- setValue,
275
- setValues,
276
- reset,
277
- submit,
278
- validate,
279
- getFieldError: field => fieldErrors[field] || computed(() => undefined),
280
- getFieldAsyncError: field => fieldAsyncErrors[field] || computed(() => undefined),
281
- getFieldTouched: field => computed(() => formGroup.get(field)?.touched ?? false),
282
- isFieldValid: field => computed(() => {
283
- const control = formGroup.get(field);
284
- return !!control && control.valid && !control.pending;
285
- }),
286
- isFieldAsyncValidating: field => computed(() => !!formGroup.get(field)?.pending),
287
- fieldErrors,
288
- fieldAsyncErrors,
289
- values: valuesTree,
290
- destroy
291
- };
292
- return formTree;
293
- }
294
- function createVirtualFormArray(items, visibleRange, controlFactory = value => new FormControl(value)) {
295
- const start = Math.max(0, visibleRange.start);
296
- const end = Math.max(start, visibleRange.end);
297
- const controls = items.slice(start, end).map((item, offset) => controlFactory(item, start + offset));
298
- return new FormArray(controls);
299
- }
300
- function enhanceArraysRecursively(obj, visited = new WeakSet()) {
301
- if (visited.has(obj)) {
302
- return;
303
- }
304
- visited.add(obj);
305
- for (const key in obj) {
306
- const value = obj[key];
307
- if (isSignal(value)) {
308
- const signalValue = value();
309
- if (Array.isArray(signalValue)) {
310
- obj[key] = enhanceArray(value);
311
- }
312
- } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
313
- enhanceArraysRecursively(value, visited);
314
- }
315
- }
316
- }
317
- const enhanceArray = arraySignal => {
318
- const enhanced = arraySignal;
319
- enhanced.push = item => {
320
- arraySignal.update(arr => [...arr, item]);
321
- };
322
- enhanced.removeAt = index => {
323
- arraySignal.update(arr => arr.filter((_, i) => i !== index));
324
- };
325
- enhanced.setAt = (index, value) => {
326
- arraySignal.update(arr => arr.map((item, i) => i === index ? value : item));
327
- };
328
- enhanced.insertAt = (index, item) => {
329
- arraySignal.update(arr => [...arr.slice(0, index), item, ...arr.slice(index)]);
330
- };
331
- enhanced.move = (from, to) => {
332
- arraySignal.update(arr => {
333
- const newArr = [...arr];
334
- const [item] = newArr.splice(from, 1);
335
- if (item !== undefined) {
336
- newArr.splice(to, 0, item);
337
- }
338
- return newArr;
339
- });
340
- };
341
- enhanced.clear = () => {
342
- arraySignal.set([]);
343
- };
344
- return enhanced;
345
- };
346
- function getSignalAtPath(node, path) {
347
- if (!path) {
348
- return null;
349
- }
350
- const segments = parsePath(path);
351
- let current = node;
352
- for (let i = 0; i < segments.length; i++) {
353
- const segment = segments[i];
354
- if (!current || typeof current !== 'object' && typeof current !== 'function') {
355
- return null;
356
- }
357
- current = current[segment];
358
- if (isSignal(current) && i < segments.length - 1) {
359
- current = current();
360
- }
361
- }
362
- if (isSignal(current)) {
363
- return current;
364
- }
365
- return null;
366
- }
367
- function joinPath(parent, segment) {
368
- return parent ? `${parent}.${segment}` : segment;
369
- }
370
- function wrapSyncValidator(validator) {
371
- return control => {
372
- const result = validator(control.value);
373
- return result ? {
374
- [SYNC_ERROR_KEY]: result
375
- } : null;
376
- };
377
- }
378
- function wrapAsyncValidator(validator) {
379
- return async control => {
380
- try {
381
- const maybeAsync = validator(control.value);
382
- const resolved = isObservable(maybeAsync) ? await firstValueFrom(maybeAsync) : await maybeAsync;
383
- return resolved ? {
384
- [ASYNC_ERROR_KEY]: resolved
385
- } : null;
386
- } catch {
387
- return {
388
- [ASYNC_ERROR_KEY]: 'Validation error'
389
- };
390
- }
391
- };
392
- }
393
- function createAbstractControl(value, path, validators, asyncValidators) {
394
- const syncValidator = findValidator(validators, path);
395
- const asyncValidator = findValidator(asyncValidators, path);
396
- const syncFns = syncValidator ? [wrapSyncValidator(syncValidator)] : undefined;
397
- const asyncFns = asyncValidator ? [wrapAsyncValidator(asyncValidator)] : undefined;
398
- if (Array.isArray(value)) {
399
- const controls = value.map((item, index) => createAbstractControl(item, joinPath(path, String(index)), validators, asyncValidators));
400
- return new FormArray(controls, syncFns, asyncFns);
401
- }
402
- if (isPlainObject(value)) {
403
- const controls = {};
404
- for (const [key, child] of Object.entries(value)) {
405
- controls[key] = createAbstractControl(child, joinPath(path, key), validators, asyncValidators);
406
- }
407
- return new FormGroup(controls, {
408
- validators: syncFns,
409
- asyncValidators: asyncFns
410
- });
411
- }
412
- return new FormControl(value, {
413
- validators: syncFns,
414
- asyncValidators: asyncFns,
415
- nonNullable: false
416
- });
417
- }
418
- function connectControlAndSignal(control, valueSignal, cleanupCallbacks, fieldConfig) {
419
- const maybeConnect = control.connect;
420
- if (typeof maybeConnect === 'function') {
421
- try {
422
- const res = maybeConnect.call(control, valueSignal);
423
- if (res && typeof res.unsubscribe === 'function') {
424
- cleanupCallbacks.push(() => res.unsubscribe());
425
- }
426
- return;
427
- } catch {}
428
- }
429
- if (isDevEnvironment() && !hasShownLegacyWarning) {
430
- hasShownLegacyWarning = true;
431
- console.warn('[@signaltree/ng-forms] Legacy Angular 17-19 support is deprecated and will be removed in v6.0. ' + 'Please upgrade to Angular 20.3+ to use native Signal Forms. ' + 'See MIGRATION.md for the upgrade path.');
432
- }
433
- let updatingFromControl = false;
434
- let updatingFromSignal = false;
435
- let versionCounter = 0;
436
- let lastControlVersion = 0;
437
- let controlDebounceTimer = null;
438
- const debounceMs = fieldConfig?.debounceMs ?? 0;
439
- const originalSet = valueSignal.set.bind(valueSignal);
440
- const originalUpdate = valueSignal.update.bind(valueSignal);
441
- const applyControlValue = value => {
442
- updatingFromSignal = true;
443
- if (!Object.is(control.value, value)) {
444
- const untypedControl = control;
445
- untypedControl.setValue(value, {
446
- emitEvent: true
447
- });
448
- untypedControl.markAsDirty();
449
- }
450
- updatingFromSignal = false;
451
- };
452
- valueSignal.set = value => {
453
- const currentVersion = ++versionCounter;
454
- originalSet(value);
455
- if (updatingFromControl) {
456
- return;
457
- }
458
- if (lastControlVersion > currentVersion) {
459
- return;
460
- }
461
- applyControlValue(value);
462
- };
463
- valueSignal.update = updater => {
464
- const next = updater(valueSignal());
465
- valueSignal.set(next);
466
- };
467
- const pushUpdateFromControl = value => {
468
- updatingFromControl = true;
469
- lastControlVersion = ++versionCounter;
470
- originalSet(value);
471
- updatingFromControl = false;
472
- };
473
- const handleControlChange = value => {
474
- if (updatingFromSignal) {
475
- return;
476
- }
477
- if (debounceMs > 0) {
478
- if (controlDebounceTimer) {
479
- clearTimeout(controlDebounceTimer);
480
- }
481
- controlDebounceTimer = setTimeout(() => {
482
- controlDebounceTimer = null;
483
- pushUpdateFromControl(value);
484
- }, debounceMs);
485
- return;
486
- }
487
- pushUpdateFromControl(value);
488
- };
489
- const subscription = control.valueChanges.subscribe(value => {
490
- handleControlChange(value);
491
- });
492
- cleanupCallbacks.push(() => {
493
- subscription.unsubscribe();
494
- valueSignal.set = originalSet;
495
- valueSignal.update = originalUpdate;
496
- if (controlDebounceTimer) {
497
- clearTimeout(controlDebounceTimer);
498
- }
499
- });
500
- }
501
- function connectFormArrayAndSignal(formArray, arraySignal, path, validators, asyncValidators, cleanupCallbacks, connectControlRecursive) {
502
- const maybeConnect = formArray.connect;
503
- if (typeof maybeConnect === 'function') {
504
- try {
505
- const res = maybeConnect.call(formArray, arraySignal);
506
- if (res && typeof res.unsubscribe === 'function') {
507
- cleanupCallbacks.push(() => res.unsubscribe());
508
- }
509
- return;
510
- } catch {}
511
- }
512
- let updatingFromControl = false;
513
- let updatingFromSignal = false;
514
- const originalSet = arraySignal.set.bind(arraySignal);
515
- const originalUpdate = arraySignal.update.bind(arraySignal);
516
- arraySignal.set = value => {
517
- originalSet(value);
518
- if (updatingFromControl) {
519
- return;
520
- }
521
- updatingFromSignal = true;
522
- syncFormArrayFromValue(formArray, value, path, validators, asyncValidators, connectControlRecursive);
523
- formArray.markAsDirty();
524
- updatingFromSignal = false;
525
- };
526
- arraySignal.update = updater => {
527
- const next = updater(arraySignal());
528
- arraySignal.set(next);
529
- };
530
- const subscription = formArray.valueChanges.subscribe(value => {
531
- if (updatingFromSignal) {
532
- return;
533
- }
534
- updatingFromControl = true;
535
- originalSet(value);
536
- updatingFromControl = false;
537
- });
538
- cleanupCallbacks.push(() => {
539
- subscription.unsubscribe();
540
- arraySignal.set = originalSet;
541
- arraySignal.update = originalUpdate;
542
- });
543
- syncFormArrayFromValue(formArray, arraySignal(), path, validators, asyncValidators, connectControlRecursive);
544
- }
545
- function syncFormArrayFromValue(formArray, nextValue, path, validators, asyncValidators, connectControlRecursive) {
546
- if (!Array.isArray(nextValue)) {
547
- nextValue = [];
548
- }
549
- while (formArray.length > nextValue.length) {
550
- formArray.removeAt(formArray.length - 1);
551
- }
552
- nextValue.forEach((item, index) => {
553
- const childPath = joinPath(path, String(index));
554
- const existing = formArray.at(index);
555
- if (!existing) {
556
- const control = createAbstractControl(item, childPath, validators, asyncValidators);
557
- formArray.insert(index, control);
558
- connectControlRecursive(control, childPath);
559
- return;
560
- }
561
- if (existing instanceof FormArray) {
562
- syncFormArrayFromValue(existing, Array.isArray(item) ? item : [], childPath, validators, asyncValidators, connectControlRecursive);
563
- return;
564
- }
565
- if (existing instanceof FormGroup) {
566
- if (isPlainObject(item)) {
567
- existing.setValue(item, {
568
- emitEvent: false
569
- });
570
- }
571
- return;
572
- }
573
- if (!Object.is(existing.value, item)) {
574
- const untypedExisting = existing;
575
- untypedExisting.setValue(item, {
576
- emitEvent: false
577
- });
578
- }
579
- });
580
- }
581
- function collectControlSnapshot(control) {
582
- const snapshot = {
583
- syncErrors: {},
584
- asyncErrors: {},
585
- touched: {},
586
- pending: {}
587
- };
588
- traverseControls(control, (currentPath, currentControl) => {
589
- if (!currentPath) {
590
- return;
591
- }
592
- if (currentControl.touched) {
593
- snapshot.touched[currentPath] = true;
594
- }
595
- if (currentControl.pending) {
596
- snapshot.pending[currentPath] = true;
597
- }
598
- const errors = currentControl.errors;
599
- if (!errors) {
600
- return;
601
- }
602
- const syncMessage = errors[SYNC_ERROR_KEY];
603
- if (typeof syncMessage === 'string') {
604
- snapshot.syncErrors[currentPath] = syncMessage;
605
- }
606
- const asyncMessage = errors[ASYNC_ERROR_KEY];
607
- if (typeof asyncMessage === 'string') {
608
- snapshot.asyncErrors[currentPath] = asyncMessage;
609
- }
610
- }, '');
611
- return snapshot;
612
- }
613
- function traverseControls(control, visitor, path = '') {
614
- visitor(path, control);
615
- if (control instanceof FormGroup) {
616
- Object.entries(control.controls).forEach(([key, child]) => {
617
- traverseControls(child, visitor, joinPath(path, key));
618
- });
619
- return;
620
- }
621
- if (control instanceof FormArray) {
622
- control.controls.forEach((child, index) => {
623
- traverseControls(child, visitor, joinPath(path, String(index)));
624
- });
625
- }
626
- }
627
- function waitForPending(control) {
628
- if (!control.pending) {
629
- return Promise.resolve();
630
- }
631
- return new Promise(resolve => {
632
- const subscription = control.statusChanges.subscribe(() => {
633
- if (!control.pending) {
634
- subscription.unsubscribe();
635
- resolve();
636
- }
637
- });
638
- });
639
- }
640
- function isPlainObject(value) {
641
- return !!value && typeof value === 'object' && !Array.isArray(value) && Object.prototype.toString.call(value) === '[object Object]';
642
- }
643
- function tryInjectDestroyRef() {
644
- try {
645
- return inject(DestroyRef);
646
- } catch {
647
- return null;
648
- }
649
- }
650
- function normalizeSyncValidators(base, fieldConfigs) {
651
- const buckets = new Map();
652
- for (const [path, validator] of Object.entries(base)) {
653
- const existing = buckets.get(path) ?? [];
654
- existing.push(validator);
655
- buckets.set(path, existing);
656
- }
657
- for (const [path, config] of Object.entries(fieldConfigs)) {
658
- const validators = toValidatorArray(config.validators);
659
- if (validators.length === 0) {
660
- continue;
661
- }
662
- const existing = buckets.get(path) ?? [];
663
- existing.push(...validators);
664
- buckets.set(path, existing);
665
- }
666
- const normalized = {};
667
- buckets.forEach((validators, path) => {
668
- normalized[path] = value => {
669
- for (const validator of validators) {
670
- const result = validator(value);
671
- if (result) {
672
- return result;
673
- }
674
- }
675
- return null;
676
- };
677
- });
678
- return {
679
- ...base,
680
- ...normalized
681
- };
682
- }
683
- function normalizeAsyncValidators(base, fieldConfigs) {
684
- const buckets = new Map();
685
- for (const [path, validator] of Object.entries(base)) {
686
- const existing = buckets.get(path) ?? [];
687
- existing.push(validator);
688
- buckets.set(path, existing);
689
- }
690
- for (const [path, config] of Object.entries(fieldConfigs)) {
691
- const validators = toValidatorArray(config.asyncValidators);
692
- if (validators.length === 0) {
693
- continue;
694
- }
695
- const existing = buckets.get(path) ?? [];
696
- existing.push(...validators);
697
- buckets.set(path, existing);
698
- }
699
- const normalized = {};
700
- buckets.forEach((validators, path) => {
701
- normalized[path] = async value => {
702
- for (const validator of validators) {
703
- const maybeAsync = validator(value);
704
- const result = isObservable(maybeAsync) ? await firstValueFrom(maybeAsync) : await maybeAsync;
705
- if (result) {
706
- return result;
707
- }
708
- }
709
- return null;
710
- };
711
- });
712
- return {
713
- ...base,
714
- ...normalized
715
- };
716
- }
717
- function toValidatorArray(input) {
718
- if (!input) {
719
- return [];
720
- }
721
- if (Array.isArray(input)) {
722
- return input;
723
- }
724
- return Object.values(input);
725
- }
726
- function hydrateInitialValues(initialValues, persistKey, storage) {
727
- const baseClone = deepClone(initialValues);
728
- if (!persistKey || !storage) {
729
- return {
730
- values: baseClone
731
- };
732
- }
733
- try {
734
- const storedRaw = storage.getItem(persistKey);
735
- if (!storedRaw) {
736
- return {
737
- values: baseClone
738
- };
739
- }
740
- const parsed = JSON.parse(storedRaw);
741
- const merged = mergeDeep(baseClone, parsed);
742
- return {
743
- values: merged
744
- };
745
- } catch {
746
- return {
747
- values: baseClone
748
- };
749
- }
750
- }
751
- function assertTreeNode(state) {
752
- if (!state || typeof state !== 'object') {
753
- throw new Error('Invalid state structure for form tree');
754
- }
755
- }
756
- function resolveFieldConfig(fieldConfigs, path) {
757
- if (fieldConfigs[path]) {
758
- return fieldConfigs[path];
759
- }
760
- const keys = Object.keys(fieldConfigs);
761
- let match;
762
- for (const key of keys) {
763
- if (!key.includes('*')) {
764
- continue;
765
- }
766
- if (matchPath(key, path)) {
767
- if (!match || match.key.length < key.length) {
768
- match = {
769
- key,
770
- config: fieldConfigs[key]
771
- };
772
- }
773
- }
774
- }
775
- if (match) {
776
- return match.config;
777
- }
778
- return fieldConfigs['*'];
779
- }
780
- function findValidator(map, path) {
781
- if (map[path]) {
782
- return map[path];
783
- }
784
- let candidate;
785
- for (const key of Object.keys(map)) {
786
- if (!key.includes('*')) {
787
- continue;
788
- }
789
- if (matchPath(key, path)) {
790
- if (!candidate || candidate.key.length < key.length) {
791
- candidate = {
792
- key,
793
- value: map[key]
794
- };
795
- }
796
- }
797
- }
798
- if (candidate) {
799
- return candidate.value;
800
- }
801
- return map['*'];
802
- }
803
- function createPersistController(formGroup, persistKey, storage, debounceMs, cleanupCallbacks) {
804
- if (!persistKey || !storage) {
805
- return {
806
- schedulePersist: () => undefined,
807
- persistImmediately: () => undefined
808
- };
809
- }
810
- let timer = null;
811
- const persist = () => {
812
- try {
813
- const payload = JSON.stringify(formGroup.getRawValue());
814
- storage.setItem(persistKey, payload);
815
- } catch {}
816
- };
817
- const schedulePersist = () => {
818
- if (debounceMs <= 0) {
819
- persist();
820
- return;
821
- }
822
- if (timer) {
823
- clearTimeout(timer);
824
- }
825
- timer = setTimeout(() => {
826
- timer = null;
827
- persist();
828
- }, debounceMs);
829
- };
830
- cleanupCallbacks.push(() => {
831
- if (timer) {
832
- clearTimeout(timer);
833
- }
834
- });
835
- return {
836
- schedulePersist,
837
- persistImmediately: persist
838
- };
839
- }
840
- let SignalValueDirective = class SignalValueDirective {
841
- signalTreeSignalValue;
842
- signalTreeSignalValueChange = new EventEmitter();
843
- elementRef = inject(ElementRef);
844
- renderer = inject(Renderer2);
845
- onChange = () => {};
846
- onTouched = () => {};
847
- ngOnInit() {
848
- effect(() => {
849
- const value = this.signalTreeSignalValue();
850
- this.renderer.setProperty(this.elementRef.nativeElement, 'value', value);
851
- });
852
- }
853
- handleChange(event) {
854
- const target = event.target;
855
- const value = target?.value;
856
- if (value !== undefined) {
857
- this.signalTreeSignalValue.set(value);
858
- this.signalTreeSignalValueChange.emit(value);
859
- this.onChange(value);
860
- }
861
- }
862
- handleBlur() {
863
- this.onTouched();
864
- }
865
- writeValue(value) {
866
- if (value !== undefined) {
867
- this.signalTreeSignalValue.set(value);
868
- }
869
- }
870
- registerOnChange(fn) {
871
- this.onChange = fn;
872
- }
873
- registerOnTouched(fn) {
874
- this.onTouched = fn;
875
- }
876
- setDisabledState(isDisabled) {
877
- this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
878
- }
879
- };
880
- __decorate([Input(), __metadata("design:type", Function)], SignalValueDirective.prototype, "signalTreeSignalValue", void 0);
881
- __decorate([Output(), __metadata("design:type", Object)], SignalValueDirective.prototype, "signalTreeSignalValueChange", void 0);
882
- __decorate([HostListener('input', ['$event']), HostListener('change', ['$event']), __metadata("design:type", Function), __metadata("design:paramtypes", [Event]), __metadata("design:returntype", void 0)], SignalValueDirective.prototype, "handleChange", null);
883
- __decorate([HostListener('blur'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0)], SignalValueDirective.prototype, "handleBlur", null);
884
- SignalValueDirective = __decorate([Directive({
885
- selector: '[signalTreeSignalValue]',
886
- providers: [{
887
- provide: NG_VALUE_ACCESSOR,
888
- useExisting: forwardRef(() => SignalValueDirective),
889
- multi: true
890
- }],
891
- standalone: true
892
- })], SignalValueDirective);
893
- const SIGNAL_FORM_DIRECTIVES = [SignalValueDirective];
894
-
895
- export { FormValidationError, SIGNAL_FORM_DIRECTIVES, SignalValueDirective, createFormTree, createVirtualFormArray };