@signaltree/ng-forms 4.0.9 → 4.0.12

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.
Files changed (41) hide show
  1. package/fesm2022/signaltree-ng-forms-src-audit.mjs +20 -0
  2. package/fesm2022/signaltree-ng-forms-src-audit.mjs.map +1 -0
  3. package/fesm2022/signaltree-ng-forms-src-history.mjs +113 -0
  4. package/fesm2022/signaltree-ng-forms-src-history.mjs.map +1 -0
  5. package/fesm2022/signaltree-ng-forms-src-rxjs.mjs +21 -0
  6. package/fesm2022/signaltree-ng-forms-src-rxjs.mjs.map +1 -0
  7. package/fesm2022/signaltree-ng-forms.mjs +1038 -0
  8. package/fesm2022/signaltree-ng-forms.mjs.map +1 -0
  9. package/index.d.ts +128 -0
  10. package/package.json +27 -6
  11. package/src/audit/index.d.ts +15 -0
  12. package/src/history/index.d.ts +24 -0
  13. package/src/rxjs/index.d.ts +6 -0
  14. package/LICENSE +0 -54
  15. package/eslint.config.mjs +0 -48
  16. package/jest.config.ts +0 -21
  17. package/ng-package.json +0 -8
  18. package/project.json +0 -48
  19. package/src/audit/audit.ts +0 -75
  20. package/src/audit/index.ts +0 -1
  21. package/src/audit/ng-package.json +0 -5
  22. package/src/core/async-validators.ts +0 -80
  23. package/src/core/ng-forms.spec.ts +0 -204
  24. package/src/core/ng-forms.ts +0 -1316
  25. package/src/core/validators.ts +0 -209
  26. package/src/history/history.ts +0 -169
  27. package/src/history/index.ts +0 -1
  28. package/src/history/ng-package.json +0 -5
  29. package/src/index.ts +0 -5
  30. package/src/rxjs/index.ts +0 -1
  31. package/src/rxjs/ng-package.json +0 -5
  32. package/src/rxjs/public-api.ts +0 -5
  33. package/src/rxjs/rxjs-bridge.ts +0 -75
  34. package/src/test-setup.ts +0 -6
  35. package/src/types/signaltree-core.d.ts +0 -11
  36. package/src/wizard/index.ts +0 -1
  37. package/src/wizard/wizard.ts +0 -145
  38. package/tsconfig.json +0 -33
  39. package/tsconfig.lib.json +0 -25
  40. package/tsconfig.lib.prod.json +0 -13
  41. package/tsconfig.spec.json +0 -17
@@ -0,0 +1,15 @@
1
+ import { Middleware } from '@signaltree/core';
2
+
3
+ interface AuditEntry<T = unknown> {
4
+ timestamp: number;
5
+ changes: Partial<T>;
6
+ metadata?: {
7
+ userId?: string;
8
+ source?: string;
9
+ description?: string;
10
+ };
11
+ }
12
+ declare function createAuditMiddleware<T>(auditLog: AuditEntry<T>[], getMetadata?: () => AuditEntry<T>['metadata']): Middleware<T>;
13
+
14
+ export { createAuditMiddleware };
15
+ export type { AuditEntry };
@@ -0,0 +1,24 @@
1
+ import { Signal } from '@angular/core';
2
+
3
+ interface FormTree<T extends Record<string, unknown>> {
4
+ form: any;
5
+ unwrap(): T;
6
+ destroy(): void;
7
+ setValues(values: Partial<T>): void;
8
+ }
9
+ interface FormHistory<T> {
10
+ past: T[];
11
+ present: T;
12
+ future: T[];
13
+ }
14
+ declare function withFormHistory<T extends Record<string, unknown>>(formTree: FormTree<T>, options?: {
15
+ capacity?: number;
16
+ }): FormTree<T> & {
17
+ undo: () => void;
18
+ redo: () => void;
19
+ clearHistory: () => void;
20
+ history: Signal<FormHistory<T>>;
21
+ };
22
+
23
+ export { withFormHistory };
24
+ export type { FormHistory };
@@ -0,0 +1,6 @@
1
+ import { Signal } from '@angular/core';
2
+ import { Observable } from 'rxjs';
3
+
4
+ declare function toObservable<T>(signal: Signal<T>): Observable<T>;
5
+
6
+ export { toObservable };
package/LICENSE DELETED
@@ -1,54 +0,0 @@
1
- BUSINESS SOURCE LICENSE 1.1
2
-
3
- Copyright (c) 2025 Jonathan D Borgia
4
-
5
- This Business Source License 1.1 ("License") governs the use of the software and associated documentation files (the "Software"). You are granted a limited license to use the Software under the terms of this License.
6
-
7
- 1. Definitions
8
-
9
- "Change Date" means the date on which the Change License set out in section 6 will apply to the Software. The Change Date for this release is 2028-09-05.
10
-
11
- "Change License" means the open source license that will apply to the Software on and after the Change Date. The Change License for this release is the MIT License.
12
-
13
- "Licensor" means the copyright owner granting rights under this License (Jonathan D Borgia).
14
-
15
- "You" ("Licensee") means an individual or legal entity exercising rights under this License who has not violated the terms of this License or had their rights terminated.
16
-
17
- 2. License Grant
18
-
19
- Subject to the terms and conditions of this License, Licensor hereby grants You a non-exclusive, non-transferable, worldwide license to use, reproduce, display, perform, and distribute the Software, and to make modifications and derivative works for internal use, until the Change Date.
20
-
21
- 3. Commercial Use
22
-
23
- You may use the Software in commercial applications, including for providing services, selling products that include the Software, or otherwise exploiting the Software commercially, subject to the other terms of this License.
24
-
25
- 4. Limitations and Conditions
26
-
27
- a. You may not remove or alter this License, the copyright notice, or notices of the Change Date.
28
-
29
- b. You may not publicly offer a modified version of the Software that would directly compete with Licensor's public offering of the Software if doing so would circumvent the intent of this License.
30
-
31
- c. Except as expressly provided in this License, no rights are granted to You under any patent or trademark of Licensor.
32
-
33
- 5. Disclaimer and Limitation of Liability
34
-
35
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. TO THE FULLEST EXTENT PERMITTED BY LAW, LICENSOR WILL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM OR RELATING TO THE SOFTWARE.
36
-
37
- 6. Change License
38
-
39
- On and after the Change Date specified above, the Software will be licensed under the Change License (MIT License) on the same terms and conditions as set forth by that Change License.
40
-
41
- 7. Governing Law
42
-
43
- This License will be governed by and construed in accordance with the laws of the State of New York, USA, without regard to conflict of law principles.
44
-
45
- 8. Accepting this License
46
-
47
- You accept this License by copying, modifying, or distributing the Software or any portion thereof.
48
-
49
- ---
50
-
51
- LICENSE NOTE
52
-
53
- - Original license file replaced on 2025-09-05 to Business Source License 1.1. Change Date: 2028-09-05. Change License: MIT.
54
- or standard modifications for your own applications.
package/eslint.config.mjs DELETED
@@ -1,48 +0,0 @@
1
- import nx from '@nx/eslint-plugin';
2
- import baseConfig from '../../eslint.config.mjs';
3
-
4
- export default [
5
- ...baseConfig,
6
- {
7
- files: ['**/*.json'],
8
- rules: {
9
- '@nx/dependency-checks': [
10
- 'error',
11
- {
12
- ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'],
13
- },
14
- ],
15
- },
16
- languageOptions: {
17
- parser: await import('jsonc-eslint-parser'),
18
- },
19
- },
20
- ...nx.configs['flat/angular'],
21
- ...nx.configs['flat/angular-template'],
22
- {
23
- files: ['**/*.ts'],
24
- rules: {
25
- '@angular-eslint/directive-selector': [
26
- 'error',
27
- {
28
- type: 'attribute',
29
- prefix: 'signalTree',
30
- style: 'camelCase',
31
- },
32
- ],
33
- '@angular-eslint/component-selector': [
34
- 'error',
35
- {
36
- type: 'element',
37
- prefix: 'signaltree',
38
- style: 'kebab-case',
39
- },
40
- ],
41
- },
42
- },
43
- {
44
- files: ['**/*.html'],
45
- // Override or add rules here
46
- rules: {},
47
- },
48
- ];
package/jest.config.ts DELETED
@@ -1,21 +0,0 @@
1
- export default {
2
- displayName: 'ng-forms',
3
- preset: '../../jest.preset.js',
4
- setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
5
- coverageDirectory: '../../coverage/packages/ng-forms',
6
- transform: {
7
- '^.+\\.(ts|mjs|js|html)$': [
8
- 'jest-preset-angular',
9
- {
10
- tsconfig: '<rootDir>/tsconfig.spec.json',
11
- stringifyContentPathRegex: '\\.(html|svg)$',
12
- },
13
- ],
14
- },
15
- transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
16
- snapshotSerializers: [
17
- 'jest-preset-angular/build/serializers/no-ng-attributes',
18
- 'jest-preset-angular/build/serializers/ng-snapshot',
19
- 'jest-preset-angular/build/serializers/html-comment',
20
- ],
21
- };
package/ng-package.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
- "dest": "../../dist/packages/ng-forms",
4
- "lib": {
5
- "entryFile": "src/index.ts"
6
- },
7
- "allowedNonPeerDependencies": ["@signaltree/shared", "@signaltree/core"]
8
- }
package/project.json DELETED
@@ -1,48 +0,0 @@
1
- {
2
- "name": "ng-forms",
3
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
- "sourceRoot": "packages/ng-forms/src",
5
- "prefix": "signaltree",
6
- "projectType": "library",
7
- "release": {
8
- "version": {
9
- "manifestRootsToUpdate": ["dist/{projectRoot}"],
10
- "currentVersionResolver": "git-tag",
11
- "fallbackCurrentVersionResolver": "disk"
12
- }
13
- },
14
- "tags": [],
15
- "targets": {
16
- "build": {
17
- "executor": "@nx/angular:package",
18
- "outputs": ["{workspaceRoot}/dist/{projectRoot}"],
19
- "options": {
20
- "project": "packages/ng-forms/ng-package.json"
21
- },
22
- "configurations": {
23
- "production": {
24
- "tsConfig": "packages/ng-forms/tsconfig.lib.prod.json"
25
- },
26
- "development": {
27
- "tsConfig": "packages/ng-forms/tsconfig.lib.json"
28
- }
29
- },
30
- "defaultConfiguration": "production"
31
- },
32
- "nx-release-publish": {
33
- "options": {
34
- "packageRoot": "dist/{projectRoot}"
35
- }
36
- },
37
- "test": {
38
- "executor": "@nx/jest:jest",
39
- "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
40
- "options": {
41
- "jestConfig": "packages/ng-forms/jest.config.ts"
42
- }
43
- },
44
- "lint": {
45
- "executor": "@nx/eslint:lint"
46
- }
47
- }
48
- }
@@ -1,75 +0,0 @@
1
- /// <reference path="../types/signaltree-core.d.ts" />
2
- import { getChanges } from '@signaltree/shared';
3
-
4
- import type { Middleware } from '@signaltree/core';
5
- /**
6
- * Audit log entry recording form changes
7
- */
8
- export interface AuditEntry<T = unknown> {
9
- /** Timestamp when change occurred */
10
- timestamp: number;
11
- /** Changed fields and their new values */
12
- changes: Partial<T>;
13
- /** Optional metadata about the change */
14
- metadata?: {
15
- /** User ID who made the change */
16
- userId?: string;
17
- /** Source of the change (e.g., 'ui', 'api', 'import') */
18
- source?: string;
19
- /** Human-readable description */
20
- description?: string;
21
- };
22
- }
23
-
24
- /**
25
- * Creates an audit middleware for form change tracking.
26
- * Automatically logs all form state changes with timestamps and optional metadata.
27
- *
28
- * @param auditLog - Array to collect audit entries
29
- * @param getMetadata - Optional function to provide metadata for each change
30
- * @returns Middleware that records form changes
31
- *
32
- * @example
33
- * ```typescript
34
- * const auditLog: AuditEntry<MyFormData>[] = [];
35
- *
36
- * const form = createFormTree(
37
- * { name: '', email: '' },
38
- * {
39
- * middleware: [
40
- * createAuditMiddleware(auditLog, () => ({
41
- * userId: currentUser.id,
42
- * source: 'form-editor'
43
- * }))
44
- * ]
45
- * }
46
- * );
47
- *
48
- * form.$.name.set('John');
49
- *
50
- * console.log(auditLog);
51
- * // [{
52
- * // timestamp: 1234567890,
53
- * // changes: { name: 'John' },
54
- * // metadata: { userId: '123', source: 'form-editor' }
55
- * // }]
56
- * ```
57
- */
58
- export function createAuditMiddleware<T>(
59
- auditLog: AuditEntry<T>[],
60
- getMetadata?: () => AuditEntry<T>['metadata']
61
- ): Middleware<T> {
62
- return {
63
- id: 'audit',
64
- after: (_action: string, _payload: unknown, oldState: T, newState: T) => {
65
- const changes = getChanges(oldState, newState);
66
- if (Object.keys(changes).length > 0) {
67
- auditLog.push({
68
- timestamp: Date.now(),
69
- changes,
70
- metadata: getMetadata?.(),
71
- });
72
- }
73
- },
74
- };
75
- }
@@ -1 +0,0 @@
1
- export { createAuditMiddleware, type AuditEntry } from './audit';
@@ -1,5 +0,0 @@
1
- {
2
- "lib": {
3
- "entryFile": "index.ts"
4
- }
5
- }
@@ -1,80 +0,0 @@
1
- import { firstValueFrom, isObservable } from 'rxjs';
2
-
3
- /**
4
- * @fileoverview Tree-shakeable async validator functions for form fields
5
- */
6
-
7
- import type { FormTreeAsyncValidatorFn } from './ng-forms';
8
-
9
- /**
10
- * Creates an async uniqueness validator
11
- *
12
- * @param checkFn - Async function that returns true if value already exists
13
- * @param message - Custom error message (default: "Already exists")
14
- * @returns Async validator function
15
- *
16
- * @example
17
- * ```typescript
18
- * createFormTree(data, {
19
- * asyncValidators: {
20
- * email: unique(
21
- * async (email) => {
22
- * const response = await fetch(`/api/check-email?email=${email}`);
23
- * const { exists } = await response.json();
24
- * return exists;
25
- * },
26
- * 'This email is already registered'
27
- * )
28
- * }
29
- * });
30
- * ```
31
- */
32
- export function unique(
33
- checkFn: (value: unknown) => Promise<boolean>,
34
- message = 'Already exists'
35
- ): FormTreeAsyncValidatorFn<unknown> {
36
- return async (value: unknown) => {
37
- if (!value) return null;
38
- const exists = await checkFn(value);
39
- return exists ? message : null;
40
- };
41
- }
42
-
43
- /**
44
- * Creates a debounced async validator
45
- *
46
- * @param validator - The async validator to debounce
47
- * @param delayMs - Delay in milliseconds before running validation
48
- * @returns Debounced async validator
49
- *
50
- * @example
51
- * ```typescript
52
- * createFormTree(data, {
53
- * asyncValidators: {
54
- * username: debounce(
55
- * unique(checkUsername, 'Username taken'),
56
- * 500 // Wait 500ms after user stops typing
57
- * )
58
- * }
59
- * });
60
- * ```
61
- */
62
- export function debounce(
63
- validator: FormTreeAsyncValidatorFn<unknown>,
64
- delayMs: number
65
- ): FormTreeAsyncValidatorFn<unknown> {
66
- let timeoutId: ReturnType<typeof setTimeout>;
67
-
68
- return async (value: unknown) => {
69
- return new Promise((resolve) => {
70
- clearTimeout(timeoutId);
71
- timeoutId = setTimeout(async () => {
72
- const maybeAsync = validator(value);
73
- const result = isObservable(maybeAsync)
74
- ? await firstValueFrom(maybeAsync)
75
- : await maybeAsync;
76
- resolve(result);
77
- }, delayMs);
78
- });
79
- };
80
- }
@@ -1,204 +0,0 @@
1
- import { AuditEntry, createAuditMiddleware } from '../audit/audit';
2
- import { toObservable } from '../rxjs/rxjs-bridge';
3
- import { unique } from './async-validators';
4
- import { createFormTree, SIGNAL_FORM_DIRECTIVES, SignalValueDirective } from './ng-forms';
5
- import { email as emailValidator, minLength, pattern, required } from './validators';
6
-
7
- interface TestFormData extends Record<string, unknown> {
8
- username: string;
9
- email: string;
10
- age: number;
11
- preferences: {
12
- newsletter: boolean;
13
- theme: string;
14
- };
15
- tags: string[];
16
- }
17
-
18
- describe('NgForms', () => {
19
- let initialFormData: TestFormData;
20
-
21
- beforeEach(() => {
22
- initialFormData = {
23
- username: '',
24
- email: '',
25
- age: 0,
26
- preferences: {
27
- newsletter: false,
28
- theme: 'light',
29
- },
30
- tags: [],
31
- };
32
- });
33
-
34
- describe('createFormTree', () => {
35
- it('should create a form tree with form-specific signals', () => {
36
- const form = createFormTree(initialFormData);
37
-
38
- expect(form.state).toBeDefined();
39
- expect(form.$).toBe(form.state); // Alias
40
- expect(form.errors).toBeDefined();
41
- expect(form.asyncErrors).toBeDefined();
42
- expect(form.touched).toBeDefined();
43
- expect(form.asyncValidating).toBeDefined();
44
- expect(form.dirty).toBeDefined();
45
- expect(form.valid).toBeDefined();
46
- expect(form.submitting).toBeDefined();
47
- });
48
-
49
- it('should support field validation', async () => {
50
- const form = createFormTree(initialFormData, {
51
- validators: {
52
- username: required('Username is required'),
53
- email: emailValidator('Invalid email'),
54
- },
55
- });
56
-
57
- form.setValue('username', '');
58
- await new Promise((resolve) => setTimeout(resolve, 0)); // Allow async validation
59
-
60
- expect(form.errors()['username']).toBe('Username is required');
61
- expect(form.valid()).toBe(false);
62
- });
63
-
64
- it('should track touched fields', () => {
65
- const form = createFormTree(initialFormData);
66
-
67
- expect(form.touched()['username']).toBeUndefined();
68
-
69
- form.setValue('username', 'test');
70
-
71
- expect(form.touched()['username']).toBe(true);
72
- });
73
-
74
- it('should mark form as dirty when values change', () => {
75
- const form = createFormTree(initialFormData);
76
-
77
- expect(form.dirty()).toBe(false);
78
-
79
- form.setValue('username', 'test');
80
-
81
- expect(form.dirty()).toBe(true);
82
- });
83
-
84
- it('should support form reset', () => {
85
- const form = createFormTree(initialFormData);
86
-
87
- form.setValue('username', 'test');
88
- form.setValue('email', 'test@example.com');
89
-
90
- expect(form.dirty()).toBe(true);
91
- expect(form.state.username()).toBe('test');
92
-
93
- form.reset();
94
-
95
- expect(form.dirty()).toBe(false);
96
- expect(form.state.username()).toBe('');
97
- expect(Object.keys(form.errors()).length).toBe(0);
98
- });
99
-
100
- it('should support async validation', async () => {
101
- const form = createFormTree(initialFormData, {
102
- asyncValidators: {
103
- username: unique(
104
- async (value: unknown) => value === 'taken',
105
- 'Username already exists'
106
- ),
107
- },
108
- });
109
-
110
- form.setValue('username', 'taken');
111
- await new Promise((resolve) => setTimeout(resolve, 100));
112
-
113
- expect(form.asyncErrors()['username']).toBe('Username already exists');
114
- expect(form.valid()).toBe(false);
115
- });
116
-
117
- it('should support form submission', async () => {
118
- const form = createFormTree(initialFormData, {
119
- validators: {
120
- username: required(),
121
- },
122
- });
123
-
124
- form.setValue('username', 'testuser');
125
- form.setValue('email', 'test@example.com');
126
-
127
- const submitData = await form.submit(async (values) => {
128
- return { success: true, data: values };
129
- });
130
-
131
- expect(submitData.success).toBe(true);
132
- expect(submitData.data['username']).toBe('testuser');
133
- });
134
- });
135
-
136
- describe('SignalValueDirective', () => {
137
- it('should be defined and exportable', () => {
138
- expect(SignalValueDirective).toBeDefined();
139
- expect(SIGNAL_FORM_DIRECTIVES).toContain(SignalValueDirective);
140
- });
141
- });
142
-
143
- describe('validators', () => {
144
- it('should provide required validator', () => {
145
- const requiredValidator = required('This field is required');
146
-
147
- expect(requiredValidator('')).toBe('This field is required');
148
- expect(requiredValidator('value')).toBe(null);
149
- });
150
-
151
- it('should provide email validator', () => {
152
- const emailValidatorFn = emailValidator();
153
-
154
- expect(emailValidatorFn('notanemail')).toBe('Invalid email');
155
- expect(emailValidatorFn('test@example.com')).toBe(null);
156
- });
157
-
158
- it('should provide minLength validator', () => {
159
- const minLengthValidator = minLength(5);
160
-
161
- expect(minLengthValidator('abc')).toBe('Min 5 characters');
162
- expect(minLengthValidator('abcdef')).toBe(null);
163
- });
164
-
165
- it('should provide pattern validator', () => {
166
- const patternValidator = pattern(/^\d+$/, 'Must be numeric');
167
-
168
- expect(patternValidator('abc')).toBe('Must be numeric');
169
- expect(patternValidator('123')).toBe(null);
170
- });
171
- });
172
-
173
- describe('asyncValidators', () => {
174
- it('should provide unique validator', async () => {
175
- const uniqueValidator = unique(
176
- async (value: unknown) => value === 'taken',
177
- 'Already exists'
178
- );
179
-
180
- expect(await uniqueValidator('available')).toBe(null);
181
- expect(await uniqueValidator('taken')).toBe('Already exists');
182
- });
183
- });
184
-
185
- describe('createAuditMiddleware', () => {
186
- it('should track changes in audit log', () => {
187
- const auditLog: AuditEntry[] = [];
188
- const middleware = createAuditMiddleware(auditLog, () => ({
189
- userId: 'test-user',
190
- }));
191
-
192
- expect(middleware.id).toBe('audit');
193
- expect(typeof middleware.after).toBe('function');
194
- });
195
- });
196
-
197
- describe('toObservable', () => {
198
- it('should convert signal to observable', (done) => {
199
- // This would need proper Angular testing setup for full test
200
- expect(typeof toObservable).toBe('function');
201
- done();
202
- });
203
- });
204
- });