@signaltree/ng-forms 9.2.1 → 9.3.0
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 +27 -1
- package/dist/signals/bridge.js +33 -0
- package/dist/signals/index.js +1 -0
- package/package.json +22 -7
- package/src/angular-22-shims.d.ts +39 -0
- package/src/signals/bridge.d.ts +4 -0
- package/src/signals/index.d.ts +1 -0
- package/dist/audit/index.js +0 -1
package/README.md
CHANGED
|
@@ -25,7 +25,30 @@ SignalTree provides a layered forms architecture:
|
|
|
25
25
|
|
|
26
26
|
**Key insight**: `form()` is self-sufficient. `formBridge()` adds Angular-specific capabilities.
|
|
27
27
|
|
|
28
|
-
## Quick Start
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
### Standalone signal-form pattern
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { signalTree, form } from '@signaltree/core';
|
|
34
|
+
import { email } from '@signaltree/ng-forms';
|
|
35
|
+
|
|
36
|
+
const tree = signalTree({
|
|
37
|
+
login: form({
|
|
38
|
+
initial: { email: '', password: '' },
|
|
39
|
+
validators: { email: email() },
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
tree.$.login.$.email.set('user@test.com');
|
|
44
|
+
tree.$.login.valid();
|
|
45
|
+
tree.$.login.validate();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This is the smallest working setup. It uses only `form()` and keeps everything
|
|
49
|
+
in signal land.
|
|
50
|
+
|
|
51
|
+
### Angular bridge pattern (recommended when you need `FormGroup` interop)
|
|
29
52
|
|
|
30
53
|
```typescript
|
|
31
54
|
import { signalTree, form } from '@signaltree/core';
|
|
@@ -72,6 +95,9 @@ class CheckoutComponent {
|
|
|
72
95
|
}
|
|
73
96
|
```
|
|
74
97
|
|
|
98
|
+
This example is intentionally wider in scope than the standalone one because it
|
|
99
|
+
adds Angular `FormGroup` interop via `formBridge()`.
|
|
100
|
+
|
|
75
101
|
## When to Use Each Layer
|
|
76
102
|
|
|
77
103
|
### form() alone (no ng-forms needed)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { validateStandardSchema, form } from '@angular/forms/signals';
|
|
2
|
+
import { toWritableSignal } from '@signaltree/core';
|
|
3
|
+
|
|
4
|
+
function applySignalTreeSchemas(fieldRoot, tree, rootPath = '') {
|
|
5
|
+
const bound = tree.schemas.boundPaths();
|
|
6
|
+
const prefix = rootPath ? rootPath + '.' : '';
|
|
7
|
+
for (const fullPath of bound) {
|
|
8
|
+
if (rootPath && !fullPath.startsWith(prefix) && fullPath !== rootPath) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const subPath = rootPath ? fullPath.slice(prefix.length) : fullPath;
|
|
12
|
+
if (!subPath) continue;
|
|
13
|
+
const schema = tree.schemas.schemaFor(fullPath);
|
|
14
|
+
if (!schema) continue;
|
|
15
|
+
const segments = subPath.split('.');
|
|
16
|
+
let cursor = fieldRoot;
|
|
17
|
+
for (const seg of segments) {
|
|
18
|
+
if (cursor === null || cursor === undefined) break;
|
|
19
|
+
if (typeof cursor !== 'object' && typeof cursor !== 'function') break;
|
|
20
|
+
cursor = cursor[seg];
|
|
21
|
+
}
|
|
22
|
+
if (cursor === null || cursor === undefined) continue;
|
|
23
|
+
validateStandardSchema(cursor, schema);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function signalFormBridge(tree, rootPath, subtree) {
|
|
27
|
+
const writable = toWritableSignal(subtree);
|
|
28
|
+
return form(writable, fieldRoot => {
|
|
29
|
+
applySignalTreeSchemas(fieldRoot, tree, rootPath);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { applySignalTreeSchemas, signalFormBridge };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { applySignalTreeSchemas, signalFormBridge } from './bridge.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/ng-forms",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.3.0",
|
|
4
4
|
"description": "Angular forms as reactive JSON. Seamless SignalTree integration with FormTree creation, validators, and form state tracking.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,20 +15,35 @@
|
|
|
15
15
|
"default": "./dist/index.js"
|
|
16
16
|
},
|
|
17
17
|
"./audit": {
|
|
18
|
-
"types": "./src/audit/
|
|
19
|
-
"import": "./dist/audit/
|
|
20
|
-
"default": "./dist/audit/
|
|
18
|
+
"types": "./src/audit/audit.d.ts",
|
|
19
|
+
"import": "./dist/audit/audit.js",
|
|
20
|
+
"default": "./dist/audit/audit.js"
|
|
21
|
+
},
|
|
22
|
+
"./signals": {
|
|
23
|
+
"types": "./src/signals/index.d.ts",
|
|
24
|
+
"import": "./dist/signals/index.js",
|
|
25
|
+
"default": "./dist/signals/index.js"
|
|
21
26
|
}
|
|
22
27
|
},
|
|
23
28
|
"peerDependencies": {
|
|
24
|
-
"@angular/core": "^20.0.0 || ^21.0.0",
|
|
25
|
-
"@angular/forms": "^20.0.0 || ^21.0.0",
|
|
29
|
+
"@angular/core": "^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
30
|
+
"@angular/forms": "^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
26
31
|
"@signaltree/core": "workspace:*",
|
|
32
|
+
"@signaltree/schema": "workspace:*",
|
|
33
|
+
"@standard-schema/spec": "^1.0.0",
|
|
27
34
|
"rxjs": "^7.0.0"
|
|
28
35
|
},
|
|
29
|
-
"peerDependenciesMeta": {
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"@signaltree/schema": {
|
|
38
|
+
"optional": true
|
|
39
|
+
},
|
|
40
|
+
"@standard-schema/spec": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
30
44
|
"devDependencies": {
|
|
31
45
|
"@signaltree/core": "workspace:*",
|
|
46
|
+
"@signaltree/schema": "workspace:*",
|
|
32
47
|
"@signaltree/shared": "workspace:*",
|
|
33
48
|
"jest-preset-angular": "^15.0.3"
|
|
34
49
|
},
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient type shim for Angular 22 Signal Forms.
|
|
3
|
+
*
|
|
4
|
+
* The bridge in this directory imports from `@angular/forms/signals`, which
|
|
5
|
+
* is a subpath that only exists in `@angular/forms@22.0.0+`. TypeScript's
|
|
6
|
+
* classic node resolution does not follow `exports` fields in package.json,
|
|
7
|
+
* so we declare a minimal ambient module here. At the user's install site
|
|
8
|
+
* (assuming they have `@angular/forms@22+` installed), this shim is shadowed
|
|
9
|
+
* by the real types.
|
|
10
|
+
*
|
|
11
|
+
* The shim deliberately uses permissive types so the bridge compiles without
|
|
12
|
+
* coupling to internal Signal Forms type machinery (which is still evolving
|
|
13
|
+
* in 22.x RCs). The bridge is type-safe at its API surface; internals are
|
|
14
|
+
* navigated via runtime property access.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
declare module '@angular/forms/signals' {
|
|
19
|
+
import type { WritableSignal } from '@angular/core';
|
|
20
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
21
|
+
|
|
22
|
+
/** Re-shape of Angular 22's `form()` factory (subset). */
|
|
23
|
+
export function form<TModel>(
|
|
24
|
+
model: WritableSignal<TModel>,
|
|
25
|
+
schemaFn?: (fieldRoot: unknown) => void,
|
|
26
|
+
): unknown;
|
|
27
|
+
|
|
28
|
+
/** Re-shape of Angular 22's `validateStandardSchema()`. */
|
|
29
|
+
export function validateStandardSchema(
|
|
30
|
+
path: unknown,
|
|
31
|
+
schema: StandardSchemaV1<unknown> | unknown,
|
|
32
|
+
): void;
|
|
33
|
+
|
|
34
|
+
/** Re-shape of Angular 22's `applyEach()` for arrays. */
|
|
35
|
+
export function applyEach(
|
|
36
|
+
path: unknown,
|
|
37
|
+
schema: unknown,
|
|
38
|
+
): void;
|
|
39
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ISignalTree } from '@signaltree/core';
|
|
2
|
+
import type { SchemaMethods } from '@signaltree/schema';
|
|
3
|
+
export declare function applySignalTreeSchemas(fieldRoot: unknown, tree: ISignalTree<unknown> & SchemaMethods, rootPath?: string): void;
|
|
4
|
+
export declare function signalFormBridge<TModel>(tree: ISignalTree<unknown> & SchemaMethods, rootPath: string, subtree: unknown): unknown;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { signalFormBridge, applySignalTreeSchemas } from './bridge';
|
package/dist/audit/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { createAuditCallback, createAuditTracker } from './audit.js';
|