@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 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 (Recommended Pattern)
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.2.1",
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/index.d.ts",
19
- "import": "./dist/audit/index.js",
20
- "default": "./dist/audit/index.js"
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';
@@ -1 +0,0 @@
1
- export { createAuditCallback, createAuditTracker } from './audit.js';