@signaltree/ng-forms 4.1.4 → 4.1.5

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