@signaltree/ng-forms 7.3.0 → 7.3.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.
- package/README.md +15 -12
- package/dist/audit/audit.js +74 -0
- package/dist/audit/index.js +1 -0
- package/dist/constants.js +6 -0
- package/dist/core/async-validators.js +24 -0
- package/dist/core/ng-forms.js +895 -0
- package/dist/core/validators.js +56 -0
- package/dist/deep-clone.js +80 -0
- package/dist/enhancer/form-bridge.js +2 -1
- package/dist/get-changes.js +11 -0
- package/dist/history/history.js +113 -0
- package/dist/index.js +2 -2
- package/dist/lru-cache.js +64 -0
- package/dist/match-path.js +13 -0
- package/dist/merge-deep.js +26 -0
- package/dist/parse-path.js +13 -0
- package/dist/snapshots-equal.js +5 -0
- package/dist/tslib.es6.js +34 -0
- package/dist/wizard/wizard.js +77 -0
- package/package.json +1 -1
- package/dist/enhancer/index.js +0 -2
package/README.md
CHANGED
|
@@ -77,11 +77,14 @@ class CheckoutComponent {
|
|
|
77
77
|
### form() alone (no ng-forms needed)
|
|
78
78
|
|
|
79
79
|
```typescript
|
|
80
|
+
import { signalTree, form } from '@signaltree/core';
|
|
81
|
+
import { email } from '@signaltree/ng-forms';
|
|
82
|
+
|
|
80
83
|
// Pure signal forms - works without Angular forms module
|
|
81
84
|
const tree = signalTree({
|
|
82
85
|
login: form({
|
|
83
86
|
initial: { email: '', password: '' },
|
|
84
|
-
validators: { email:
|
|
87
|
+
validators: { email: email() },
|
|
85
88
|
}),
|
|
86
89
|
});
|
|
87
90
|
|
|
@@ -137,9 +140,9 @@ pnpm add @signaltree/core @signaltree/ng-forms
|
|
|
137
140
|
|
|
138
141
|
```typescript
|
|
139
142
|
import { Component } from '@angular/core';
|
|
140
|
-
import { createFormTree,
|
|
143
|
+
import { createFormTree, required, email } from '@signaltree/ng-forms';
|
|
141
144
|
|
|
142
|
-
interface ProfileForm {
|
|
145
|
+
interface ProfileForm extends Record<string, unknown> {
|
|
143
146
|
name: string;
|
|
144
147
|
email: string;
|
|
145
148
|
marketing: boolean;
|
|
@@ -182,9 +185,9 @@ export class ProfileFormComponent {
|
|
|
182
185
|
persistKey: 'profile-form',
|
|
183
186
|
storage: this.storage,
|
|
184
187
|
fieldConfigs: {
|
|
185
|
-
name: { validators:
|
|
188
|
+
name: { validators: [required('Name is required')] },
|
|
186
189
|
email: {
|
|
187
|
-
validators: [
|
|
190
|
+
validators: [required(), email()],
|
|
188
191
|
debounceMs: 150,
|
|
189
192
|
},
|
|
190
193
|
},
|
|
@@ -256,8 +259,8 @@ class CheckoutComponent {
|
|
|
256
259
|
}, {
|
|
257
260
|
persistKey: 'checkout-draft',
|
|
258
261
|
fieldConfigs: {
|
|
259
|
-
'shipping.zip': { validators:
|
|
260
|
-
'payment.card': { validators:
|
|
262
|
+
'shipping.zip': { validators: [(v) => /^\d{5}$/.test(String(v)) ? null : 'Invalid ZIP'] },
|
|
263
|
+
'payment.card': { validators: [(v) => /^\d{13,19}$/.test(String(v)) ? null : 'Invalid card'], debounceMs: 300 }
|
|
261
264
|
}
|
|
262
265
|
});
|
|
263
266
|
|
|
@@ -287,7 +290,7 @@ const checkout = createFormTree(initialState, {
|
|
|
287
290
|
},
|
|
288
291
|
fieldConfigs: {
|
|
289
292
|
'payment.card.number': { debounceMs: 200 },
|
|
290
|
-
'preferences.*': { validators:
|
|
293
|
+
'preferences.*': { validators: [required()] },
|
|
291
294
|
},
|
|
292
295
|
conditionals: [
|
|
293
296
|
{
|
|
@@ -399,7 +402,7 @@ Use `SignalValueDirective` to keep standalone signals and `ngModel` fields align
|
|
|
399
402
|
### Before (deprecated)
|
|
400
403
|
|
|
401
404
|
```typescript
|
|
402
|
-
import { createFormTree } from '@signaltree/ng-forms';
|
|
405
|
+
import { createFormTree, email } from '@signaltree/ng-forms';
|
|
403
406
|
|
|
404
407
|
const form = createFormTree(
|
|
405
408
|
{
|
|
@@ -407,7 +410,7 @@ const form = createFormTree(
|
|
|
407
410
|
email: '',
|
|
408
411
|
},
|
|
409
412
|
{
|
|
410
|
-
validators: { email:
|
|
413
|
+
validators: { email: email() },
|
|
411
414
|
persistKey: 'profile-form',
|
|
412
415
|
}
|
|
413
416
|
);
|
|
@@ -421,12 +424,12 @@ form.form; // FormGroup
|
|
|
421
424
|
|
|
422
425
|
```typescript
|
|
423
426
|
import { signalTree, form } from '@signaltree/core';
|
|
424
|
-
import { formBridge } from '@signaltree/ng-forms';
|
|
427
|
+
import { formBridge, email } from '@signaltree/ng-forms';
|
|
425
428
|
|
|
426
429
|
const tree = signalTree({
|
|
427
430
|
profile: form({
|
|
428
431
|
initial: { name: '', email: '' },
|
|
429
|
-
validators: { email:
|
|
432
|
+
validators: { email: email() },
|
|
430
433
|
persist: 'profile-form',
|
|
431
434
|
}),
|
|
432
435
|
}).with(formBridge());
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { getChanges } from '../get-changes.js';
|
|
2
|
+
|
|
3
|
+
function createAuditTracker(tree, auditLog, config = {}) {
|
|
4
|
+
const {
|
|
5
|
+
getMetadata,
|
|
6
|
+
includePreviousValues = false,
|
|
7
|
+
filter,
|
|
8
|
+
maxEntries = 0
|
|
9
|
+
} = config;
|
|
10
|
+
let previousState = structuredClone(tree());
|
|
11
|
+
let isTracking = true;
|
|
12
|
+
const handleChange = () => {
|
|
13
|
+
if (!isTracking) return;
|
|
14
|
+
const currentState = tree();
|
|
15
|
+
const changes = getChanges(previousState, currentState);
|
|
16
|
+
if (Object.keys(changes).length > 0) {
|
|
17
|
+
if (filter && !filter(changes)) {
|
|
18
|
+
previousState = structuredClone(currentState);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const entry = {
|
|
22
|
+
timestamp: Date.now(),
|
|
23
|
+
changes,
|
|
24
|
+
metadata: getMetadata?.()
|
|
25
|
+
};
|
|
26
|
+
if (includePreviousValues) {
|
|
27
|
+
const prevValues = {};
|
|
28
|
+
for (const key of Object.keys(changes)) {
|
|
29
|
+
prevValues[key] = previousState[key];
|
|
30
|
+
}
|
|
31
|
+
entry.previousValues = prevValues;
|
|
32
|
+
}
|
|
33
|
+
auditLog.push(entry);
|
|
34
|
+
if (maxEntries > 0 && auditLog.length > maxEntries) {
|
|
35
|
+
auditLog.splice(0, auditLog.length - maxEntries);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
previousState = structuredClone(currentState);
|
|
39
|
+
};
|
|
40
|
+
let unsubscribe;
|
|
41
|
+
let pollingId;
|
|
42
|
+
if ('subscribe' in tree && typeof tree.subscribe === 'function') {
|
|
43
|
+
try {
|
|
44
|
+
unsubscribe = tree.subscribe(handleChange);
|
|
45
|
+
} catch {
|
|
46
|
+
pollingId = setInterval(handleChange, 100);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
pollingId = setInterval(handleChange, 100);
|
|
50
|
+
}
|
|
51
|
+
return () => {
|
|
52
|
+
isTracking = false;
|
|
53
|
+
if (unsubscribe) {
|
|
54
|
+
unsubscribe();
|
|
55
|
+
}
|
|
56
|
+
if (pollingId) {
|
|
57
|
+
clearInterval(pollingId);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function createAuditCallback(auditLog, getMetadata) {
|
|
62
|
+
return (previousState, currentState) => {
|
|
63
|
+
const changes = getChanges(previousState, currentState);
|
|
64
|
+
if (Object.keys(changes).length > 0) {
|
|
65
|
+
auditLog.push({
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
changes,
|
|
68
|
+
metadata: getMetadata?.()
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { createAuditCallback, createAuditTracker };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createAuditCallback, createAuditTracker } from './audit.js';
|
|
@@ -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 };
|